tools v5.6

This commit is contained in:
Apprentice Alf 2013-01-19 14:50:57 +00:00
parent 602ff30b3a
commit c23b903420
51 changed files with 4335 additions and 1071 deletions

View File

@ -1,4 +1,4 @@
Ignoble Epub DeDRM - ignobleepub_v02.5_plugin.zip Ignoble Epub DeDRM - ignobleepub_v02.6_plugin.zip
================================================= =================================================
All credit given to i♥cabbages for the original standalone scripts. I had the much easier job of converting them to a calibre plugin. All credit given to i♥cabbages for the original standalone scripts. I had the much easier job of converting them to a calibre plugin.
@ -9,7 +9,7 @@ This plugin is meant to decrypt Barnes & Noble Epubs that are protected with Ado
Installation Installation
------------ ------------
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. 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.6_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
Customization Customization
@ -30,7 +30,7 @@ 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. 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.
* 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. * 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. * 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. For some B&N accounts, the name to use is the name used in the default shipping address. For some B&N accounts, the name to use is the name used for the default Credit Card.
* 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. * 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. Click the 'OK" button to create and store the generated key. Or Cancel if you didn't want to create a key.

View File

@ -1,4 +1,4 @@
Inept Epub DeDRM - ineptepub_v02.0_plugin.zip Inept Epub DeDRM - ineptepub_v02.1_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.
@ -9,7 +9,7 @@ This plugin is meant to decrypt Adobe Digital Edition Epubs that are protected w
Installation Installation
------------ ------------
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. 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.1_plugin.zip) and click the 'Add' button. Click 'Yes' in the the "Are you sure?" dialog. Click OK in the "Success" dialog.
@ -55,7 +55,7 @@ Paste the information into a comment at my blog, http://apprenticealf.wordpress.
Linux and Adobe Digital Editions ePubs 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!) Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien and Fadel!)
1. download the most recent version of wine from winehq.org (1.3.29 in my case) 1. download the most recent version of wine from winehq.org (1.3.29 in my case)
@ -81,8 +81,7 @@ again as root use
'apt-get install python-tk 'apt-get install python-tk
4. all programms need to be installed as normal user. All these programm are installed the same way: 4. all programms need to be installed as normal user. The .exe files are installed using wine <filename> but .msi files must be installed using wine start <filename>
wine
we need: we need:
a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html) 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) (there is a “cant install ADE” site, where the setup.exe hides)

View File

@ -54,7 +54,7 @@ Paste the information into a comment at my blog, http://apprenticealf.wordpress.
Linux and Adobe Digital Editions PDFs 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!) Here are the instructions for using the tools with ePub books and Adobe Digital Editions on Linux under Wine. (Thank you mclien and Fadel!)
1. download the most recent version of wine from winehq.org (1.3.29 in my case) 1. download the most recent version of wine from winehq.org (1.3.29 in my case)
@ -80,10 +80,9 @@ again as root use
'apt-get install python-tk 'apt-get install python-tk
4. all programms need to be installed as normal user. All these programm are installed the same way: 4. all programms need to be installed as normal user. The .exe files are installed using wine <filename> but .msi files must be installed using wine start <filename>
wine
we need: we need:
a) Adobe Digital Edition 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html) a) Adobe Digital Editions 1.7.2(from: http://kb2.adobe.com/cps/403/kb403051.html)
(there is a “cant install ADE” site, where the setup.exe hides) (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) b) ActivePython-2.7.2.5-win32-x86.msi (from: http://www.activestate.com/activepython/downloads)

View File

@ -1,4 +1,4 @@
Kindle and Mobipocket Plugin - K4MobiDeDRM_v04.10_plugin.zip Kindle and Mobipocket Plugin - K4MobiDeDRM_v04.18_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. 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.
@ -13,7 +13,7 @@ This plugin is meant to remove the DRM from .prc, .mobi, .azw, .azw1, .azw3, .az
Installation Installation
------------ ------------
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. 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.18_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. Make sure that you delete any old versions of the plugin. They might interfere with the operation of the new one.

View File

@ -22,13 +22,18 @@ __docformat__ = 'restructuredtext en'
# 0.4.11 - Fixed Linux support of K4PC # 0.4.11 - Fixed Linux support of K4PC
# 0.4.12 - More Linux Wine fixes # 0.4.12 - More Linux Wine fixes
# 0.4.13 - Ancient Mobipocket files fix # 0.4.13 - Ancient Mobipocket files fix
# 0.4.14 - Error on invalid character in book names fix
# 0.4.15 - Another Topaz fix
# 0.4.16 - Yet another Topaz fix
# 0.4.17 - Manage to include the actual fix.
# 0.4.18 - More Topaz fixes
""" """
Decrypt Amazon Kindle and Mobipocket encrypted ebooks. Decrypt Amazon Kindle and Mobipocket encrypted ebooks.
""" """
PLUGIN_NAME = u"Kindle and Mobipocket DeDRM" PLUGIN_NAME = u"Kindle and Mobipocket DeDRM"
PLUGIN_VERSION_TUPLE = (0, 4, 13) PLUGIN_VERSION_TUPLE = (0, 4, 18)
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE]) PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
import sys, os, re import sys, os, re
@ -170,7 +175,7 @@ class K4DeDRM(FileTypePlugin):
print u"{0} v{1}: Successfully decrypted book after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-starttime) 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()) of = self.temporary_file(u"decrypted_ebook.{0}".format(book.getBookExtension()))
book.getFile(of.name) book.getFile(of.name)
book.cleanup() book.cleanup()
return of.name return of.name

View File

@ -255,13 +255,15 @@ class PageParser(object):
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
'img' : (1, 'snippets', 1, 0), 'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0), 'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0), 'img.y' : (1, 'scalar_number', 0, 0),
'img.h' : (1, 'scalar_number', 0, 0), 'img.h' : (1, 'scalar_number', 0, 0),
'img.w' : (1, 'scalar_number', 0, 0), 'img.w' : (1, 'scalar_number', 0, 0),
'img.src' : (1, 'scalar_number', 0, 0), 'img.src' : (1, 'scalar_number', 0, 0),
'img.color_src' : (1, 'scalar_number', 0, 0), 'img.color_src' : (1, 'scalar_number', 0, 0),
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
'paragraph' : (1, 'snippets', 1, 0), 'paragraph' : (1, 'snippets', 1, 0),
'paragraph.class' : (1, 'scalar_text', 0, 0), 'paragraph.class' : (1, 'scalar_text', 0, 0),
@ -307,6 +309,7 @@ class PageParser(object):
'span.gridEndCenter' : (1, 'scalar_number', 0, 0), 'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.class' : (1, 'scalar_text', 0, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),

View File

@ -387,10 +387,14 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) # we only handle spaceafter word class
if space == '' : space = '0' try:
if (cname == 'spaceafter') and (int(space) > 0) : (cname, space) = argres.split('-',1)
word_class = 'sa' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa'
except:
pass
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))

View File

@ -117,7 +117,7 @@ class Dictionary(object):
self.pos = val self.pos = val
return self.stable[self.pos] return self.stable[self.pos]
else: else:
print "Error - %d outside of string table limits" % val print "Error: %d outside of string table limits" % val
raise TpzDRMError('outside or string table limits') raise TpzDRMError('outside or string table limits')
# sys.exit(-1) # sys.exit(-1)
def getSize(self): def getSize(self):

View File

@ -50,8 +50,9 @@ from __future__ import with_statement
# 4.7 - Added timing reports, and changed search for Mac key files # 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 # 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. # - Moved back into plugin, __init__ in plugin now only contains plugin code.
# 4.9 - Missed some invalid characters in cleanup_name
__version__ = '4.8' __version__ = '4.9'
import sys, os, re import sys, os, re
@ -144,7 +145,7 @@ def unicode_argv():
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name): def cleanup_name(name):
# substitute filename unfriendly characters # 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"\'") 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"\'").replace(u"*",u"_").replace(u"?",u"")
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime) book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e: except Exception, e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
traceback.print_exc()
return 1 return 1
# if we're saving to the same folder as the original, use file name_ # if we're saving to the same folder as the original, use file name_

View File

@ -44,13 +44,14 @@ __docformat__ = 'restructuredtext en'
# - added ability to rename existing keys. # - added ability to rename existing keys.
# 0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and # 0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and
# - ignoblekeygen 2.4 and later. # - ignoblekeygen 2.4 and later.
# 0.2.6 - Tweaked to eliminate issue with both ignoble and inept calibre plugins installed/enabled at once
""" """
Decrypt Barnes & Noble ADEPT encrypted EPUB books. Decrypt Barnes & Noble ADEPT encrypted EPUB books.
""" """
PLUGIN_NAME = u"Ignoble Epub DeDRM" PLUGIN_NAME = u"Ignoble Epub DeDRM"
PLUGIN_VERSION_TUPLE = (0, 2, 5) PLUGIN_VERSION_TUPLE = (0, 2, 6)
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE]) PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
# Include an html helpfile in the plugin's zipfile with the following name. # Include an html helpfile in the plugin's zipfile with the following name.
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm' RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
@ -138,10 +139,7 @@ class IgnobleDeDRM(FileTypePlugin):
#check the book #check the book
from calibre_plugins.ignobleepub import ignobleepub from calibre_plugins.ignobleepub import ignobleepub
if not ignobleepub.ignobleBook(inf.name): 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)) raise IGNOBLEError(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). # Attempt to decrypt epub with each encryption key (generated or provided).
for keyname, userkey in cfg.prefs['keys'].items(): for keyname, userkey in cfg.prefs['keys'].items():
@ -152,30 +150,21 @@ class IgnobleDeDRM(FileTypePlugin):
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
result = ignobleepub.decryptBook(userkey, inf.name, of.name) result = ignobleepub.decryptBook(userkey, inf.name, of.name)
# Ebook is not a B&N epub... do nothing and pass it on. of.close()
# 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 # Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process. # file to Calibre's import process.
if result[0] == 0: if result == 0:
print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION) print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close()
return of.name return of.name
break break
print u"{0} v{1}: {2}".format(PLUGIN_NAME, PLUGIN_VERSION, result[1]) print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close()
# Something went wrong with decryption. # Something went wrong with decryption.
# Import the original unmolested epub. # Import the original unmolested epub.
print(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION)) raise IGNOBLEError(u"{0} v{1}: Ultimately failed to decrypt".format(PLUGIN_NAME, PLUGIN_VERSION))
return path_to_ebook return
def is_customizable(self): def is_customizable(self):
# return true to allow customization via the Plugin->Preferences. # return true to allow customization via the Plugin->Preferences.

View File

@ -1,420 +1,98 @@
#!/usr/bin/env python <?xml version="1.0" encoding="utf-8"?>
# -*- coding: utf-8 -*- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
from __future__ import with_statement <html xmlns="http://www.w3.org/1999/xhtml">
# ignobleepub.pyw, version 3.6 <head>
# Copyright © 2009-2010 by i♥cabbages <title>Ignoble Epub DeDRM Plugin Configuration</title>
</head>
# Released under the terms of the GNU General Public Licence, version 3 <body>
# <http://www.gnu.org/licenses/>
# Modified 20102012 by some_updates, DiapDealer and Apprentice Alf <h1>Ignoble Epub DeDRM Plugin</h1>
<h3>(version 0.2.6)</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>
# Windows users: Before running this program, you must first install Python 2.6 <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>
# 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: <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>
# 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
""" <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>
Decrypt Barnes & Noble encrypted ePub books.
"""
__license__ = 'GPL v3' <h3>Installation:</h3>
__version__ = "3.6"
import sys <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>
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 <h3>Configuration:</h3>
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW <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>
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW <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>
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW() <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>
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]
<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>
class IGNOBLEError(Exception): <h4 style="margin-left: 1.0em;"><u>Creating New Keys:</u></h4>
pass
def _load_crypto_libcrypto(): <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>
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \ <ul style="margin-left: 2.0em;">
Structure, c_ulong, create_string_buffer, cast <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>
from ctypes.util import find_library <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>
if iswindows: <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>
libcrypto = find_library('libeay32')
else:
libcrypto = find_library('crypto')
if libcrypto is None: <h4 style="margin-left: 1.0em;"><u>Deleting Keys:</u></h4>
raise IGNOBLEError('libcrypto not found')
libcrypto = CDLL(libcrypto)
AES_MAXNR = 14 <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>
c_char_pp = POINTER(c_char_p) <h4 style="margin-left: 1.0em;"><u>Exporting Keys:</u></h4>
c_int_p = POINTER(c_int)
class AES_KEY(Structure): <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>
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
('rounds', c_int)]
AES_KEY_p = POINTER(AES_KEY)
def F(restype, name, argtypes): <h4 style="margin-left: 1.0em;"><u>Importing Existing Keyfiles:</u></h4>
func = getattr(libcrypto, name)
func.restype = restype
func.argtypes = argtypes
return func
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key', <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>
[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): <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>
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): <h3>Troubleshooting:</h3>
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 <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>
def _load_crypto_pycrypto(): <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>
from Crypto.Cipher import AES as _AES
class AES(object): <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>
def __init__(self, key): <p>&nbsp;</p>
self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16) <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>
def decrypt(self, data): <p>&nbsp;</p>
return self._aes.decrypt(data) <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.
0.2.5 - Major code change to use unaltered ignobleepub.py 3.6 and
- ignoblekeygen 2.4 and later.
0.2.6 - Modified to alleviate the issue with having both the ignoble and inept epub plugins installed/enabled
</pre>
</body>
return AES </html>
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

@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
# ignoblekeygen.pyw, version 2.5 # ignobleepub.pyw, version 3.7
# Copyright © 2009-2010 by i♥cabbages # Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -15,31 +15,39 @@ from __future__ import with_statement
# from <http://www.python.org/download/> and PyCrypto from # from <http://www.python.org/download/> and PyCrypto from
# <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to # <http://www.voidspace.org.uk/python/modules.shtml#pycrypto> (make sure to
# install the version for Python 2.6). Save this script file as # install the version for Python 2.6). Save this script file as
# ignoblekeygen.pyw and double-click on it to run it. # ineptepub.pyw and double-click on it to run it.
# #
# Mac OS X users: Save this script file as ignoblekeygen.pyw. You can run this # Mac OS X users: Save this script file as ineptepub.pyw. You can run this
# program from the command line (pythonw ignoblekeygen.pyw) or by double-clicking # program from the command line (pythonw ineptepub.pyw) or by double-clicking
# it when it has been associated with PythonLauncher. # it when it has been associated with PythonLauncher.
# Revision history: # Revision history:
# 1 - Initial release # 1 - Initial release
# 2 - Add OS X support by using OpenSSL when available (taken/modified from ineptepub v5) # 2 - Added OS X support by using OpenSSL when available
# 2.1 - Allow Windows versions of libcrypto to be found # 3 - screen out improper key lengths to prevent segfaults on Linux
# 2.2 - On Windows try PyCrypto first and then OpenSSL next # 3.1 - Allow Windows versions of libcrypto to be found
# 2.3 - Modify interface to allow use of import # 3.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 2.4 - Improvements to UI and now works in plugins # 3.3 - On Windows try PyCrypto first, OpenSSL next
# 2.5 - Additional improvement for unicode and plugin support # 3.4 - Modify interface 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
# 3.7 - Tweaked to match ineptepub more closely
""" """
Generate Barnes & Noble EPUB user key from name and credit card number. Decrypt Barnes & Noble encrypted ePub books.
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "2.5" __version__ = "3.7"
import sys import sys
import os import os
import hashlib 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 # Wrap a stream so that output gets flushed immediately
# and also make sure that any unicode strings get # and also make sure that any unicode strings get
@ -58,8 +66,11 @@ class SafeUnbuffered:
def __getattr__(self, attr): def __getattr__(self, attr):
return getattr(self.stream, attr) return getattr(self.stream, attr)
iswindows = sys.platform.startswith('win') try:
isosx = sys.platform.startswith('darwin') from calibre.constants import iswindows, isosx
except:
iswindows = sys.platform.startswith('win')
isosx = sys.platform.startswith('darwin')
def unicode_argv(): def unicode_argv():
if iswindows: if iswindows:
@ -90,9 +101,7 @@ def unicode_argv():
start = argc.value - len(sys.argv) start = argc.value - len(sys.argv)
return [argv[i] for i in return [argv[i] for i in
xrange(start, argc.value)] xrange(start, argc.value)]
# if we don't have any arguments at all, just pass back script name return [u"ineptepub.py"]
# this should never happen
return [u"ignoblekeygen.py"]
else: else:
argvencoding = sys.stdin.encoding argvencoding = sys.stdin.encoding
if argvencoding == None: if argvencoding == None:
@ -133,26 +142,29 @@ def _load_crypto_libcrypto():
func.argtypes = argtypes func.argtypes = argtypes
return func return func
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key', AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
[c_char_p, c_int, AES_KEY_p]) [c_char_p, c_int, AES_KEY_p])
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt', AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p, [c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
c_int]) c_int])
class AES(object): class AES(object):
def __init__(self, userkey, iv): def __init__(self, userkey):
self._blocksize = len(userkey) self._blocksize = len(userkey)
self._iv = iv if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
raise IGNOBLEError('AES improper key used')
return
key = self._key = AES_KEY() key = self._key = AES_KEY()
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key) rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
if rv < 0: if rv < 0:
raise IGNOBLEError('Failed to initialize AES Encrypt key') raise IGNOBLEError('Failed to initialize AES key')
def encrypt(self, data): def decrypt(self, data):
out = create_string_buffer(len(data)) out = create_string_buffer(len(data))
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1) iv = ("\x00" * self._blocksize)
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
if rv == 0: if rv == 0:
raise IGNOBLEError('AES encryption failed') raise IGNOBLEError('AES decryption failed')
return out.raw return out.raw
return AES return AES
@ -161,11 +173,11 @@ def _load_crypto_pycrypto():
from Crypto.Cipher import AES as _AES from Crypto.Cipher import AES as _AES
class AES(object): class AES(object):
def __init__(self, key, iv): def __init__(self, key):
self._aes = _AES.new(key, _AES.MODE_CBC, iv) self._aes = _AES.new(key, _AES.MODE_CBC, '\x00'*16)
def encrypt(self, data): def decrypt(self, data):
return self._aes.encrypt(data) return self._aes.decrypt(data)
return AES return AES
@ -184,78 +196,151 @@ def _load_crypto():
AES = _load_crypto() AES = _load_crypto()
def normalize_name(name): META_NAMES = ('mimetype', 'META-INF/rights.xml', 'META-INF/encryption.xml')
return ''.join(x for x in name.lower() if x != ' ') 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
def generate_key(name, ccn): class Decryptor(object):
# remove spaces and case from name and CC numbers. def __init__(self, bookkey, encryption):
if type(name)==unicode: enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
name = name.encode('utf-8') self._aes = AES(bookkey)
if type(ccn)==unicode: encryption = etree.fromstring(encryption)
ccn = ccn.encode('utf-8') 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)
name = normalize_name(name) + '\x00' def decompress(self, bytes):
ccn = normalize_name(ccn) + '\x00' dc = zlib.decompressobj(-15)
bytes = dc.decompress(bytes)
ex = dc.decompress('Z') + dc.flush()
if ex:
bytes = bytes + ex
return bytes
name_sha = hashlib.sha1(name).digest()[:16] def decrypt(self, path, data):
ccn_sha = hashlib.sha1(ccn).digest()[:16] if path in self._encrypted:
both_sha = hashlib.sha1(name + ccn).digest() data = self._aes.decrypt(data)[16:]
aes = AES(ccn_sha, name_sha) data = data[:-ord(data[-1])]
crypt = aes.encrypt(both_sha + ('\x0c' * 0x0c)) data = self.decompress(data)
userkey = hashlib.sha1(crypt).digest() return data
return userkey.encode('base64')
# 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
def decryptBook(keyb64, inpath, outpath):
if AES is None:
raise IGNOBLEError(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:
print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
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))
if len(bookkey) != 64:
print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
return 1
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:
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=unicode_argv()): def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0]) 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: if len(argv) != 4:
print u"usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname) print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
return 1 return 1
name, ccn, keypath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = generate_key(name, ccn) userkey = open(keypath,'rb').read()
open(keypath,'wb').write(userkey) result = decryptBook(userkey, inpath, outpath)
return 0 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(): def gui_main():
import Tkinter import Tkinter
import Tkconstants import Tkconstants
import tkFileDialog import tkFileDialog
import tkMessageBox import traceback
class DecryptionDialog(Tkinter.Frame): class DecryptionDialog(Tkinter.Frame):
def __init__(self, root): def __init__(self, root):
Tkinter.Frame.__init__(self, root, border=5) Tkinter.Frame.__init__(self, root, border=5)
self.status = Tkinter.Label(self, text=u"Enter parameters") self.status = Tkinter.Label(self, text=u"Select files for decryption")
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
body.pack(fill=Tkconstants.X, expand=1) body.pack(fill=Tkconstants.X, expand=1)
sticky = Tkconstants.E + Tkconstants.W sticky = Tkconstants.E + Tkconstants.W
body.grid_columnconfigure(1, weight=2) body.grid_columnconfigure(1, weight=2)
Tkinter.Label(body, text=u"Account Name").grid(row=0) Tkinter.Label(body, text=u"Key file").grid(row=0)
self.name = Tkinter.Entry(body, width=40) self.keypath = Tkinter.Entry(body, width=30)
self.name.grid(row=0, column=1, sticky=sticky) self.keypath.grid(row=0, column=1, sticky=sticky)
Tkinter.Label(body, text=u"CC#").grid(row=1) if os.path.exists(u"bnepubkey.b64"):
self.ccn = Tkinter.Entry(body, width=40) self.keypath.insert(0, u"bnepubkey.b64")
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 = 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) button.grid(row=2, column=2)
buttons = Tkinter.Frame(self) buttons = Tkinter.Frame(self)
buttons.pack() buttons.pack()
botton = Tkinter.Button( botton = Tkinter.Button(
buttons, text=u"Generate", width=10, command=self.generate) buttons, text=u"Decrypt", width=10, command=self.decrypt)
botton.pack(side=Tkconstants.LEFT) botton.pack(side=Tkconstants.LEFT)
Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT) Tkinter.Frame(buttons, width=10).pack(side=Tkconstants.LEFT)
button = Tkinter.Button( button = Tkinter.Button(
@ -263,8 +348,8 @@ def gui_main():
button.pack(side=Tkconstants.RIGHT) button.pack(side=Tkconstants.RIGHT)
def get_keypath(self): def get_keypath(self):
keypath = tkFileDialog.asksaveasfilename( keypath = tkFileDialog.askopenfilename(
parent=None, title=u"Select B&N ePub key file to produce", parent=None, title=u"Select Barnes & Noble \'.b64\' key file",
defaultextension=u".b64", defaultextension=u".b64",
filetypes=[('base64-encoded files', '.b64'), filetypes=[('base64-encoded files', '.b64'),
('All Files', '.*')]) ('All Files', '.*')])
@ -274,37 +359,56 @@ def gui_main():
self.keypath.insert(0, keypath) self.keypath.insert(0, keypath)
return return
def generate(self): def get_inpath(self):
name = self.name.get() inpath = tkFileDialog.askopenfilename(
ccn = self.ccn.get() 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() keypath = self.keypath.get()
if not name: inpath = self.inpath.get()
self.status['text'] = u"Name not specified" outpath = self.outpath.get()
if not keypath or not os.path.exists(keypath):
self.status['text'] = u"Specified key file does not exist"
return return
if not ccn: if not inpath or not os.path.exists(inpath):
self.status['text'] = u"Credit card number not specified" self.status['text'] = u"Specified input file does not exist"
return return
if not keypath: if not outpath:
self.status['text'] = u"Output keyfile path not specified" self.status['text'] = u"Output file not specified"
return return
self.status['text'] = u"Generating..." 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: try:
userkey = generate_key(name, ccn) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception, e:
self.status['text'] = u"Error: (0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
open(keypath,'wb').write(userkey) if decrypt_status == 0:
self.status['text'] = u"Keyfile successfully generated" self.status['text'] = u"File successfully decrypted"
else:
self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
if AES is None: root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))
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.resizable(True, False)
root.minsize(300, 0) root.minsize(300, 0)
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1) DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)

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

@ -1,155 +1,39 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys from __future__ import with_statement
import zlib
import zipfilerugged
import os
import os.path
import getopt
from struct import unpack
__license__ = 'GPL v3'
_FILENAME_LEN_OFFSET = 26 DETAILED_MESSAGE = \
_EXTRA_LEN_OFFSET = 28 'You have personal information stored in this plugin\'s customization '+ \
_FILENAME_OFFSET = 30 'string from a previous version of this plugin.\n\n'+ \
_MAX_SIZE = 64 * 1024 'This new version of the plugin can convert that info '+ \
_MIMETYPE = 'application/epub+zip' '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.'
class ZipInfo(zipfilerugged.ZipInfo): def uStrCmp (s1, s2, caseless=False):
def __init__(self, *args, **kwargs): import unicodedata as ud
if 'compress_type' in kwargs: str1 = s1 if isinstance(s1, unicode) else unicode(s1)
compress_type = kwargs.pop('compress_type') str2 = s2 if isinstance(s2, unicode) else unicode(s2)
super(ZipInfo, self).__init__(*args, **kwargs) if caseless:
self.compress_type = compress_type return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
else:
return ud.normalize('NFC', str1) == ud.normalize('NFC', str2)
class fixZip: def parseCustString(keystuff):
def __init__(self, zinput, zoutput): userkeys = []
self.ztype = 'zip' ar = keystuff.split(':')
if zinput.lower().find('.epub') >= 0 : for i in ar:
self.ztype = 'epub' try:
self.inzip = zipfilerugged.ZipFile(zinput,'r') name, ccn = i.split(',')
self.outzip = zipfilerugged.ZipFile(zoutput,'w') # Generate Barnes & Noble EPUB user key from name and credit card number.
# open the input zip for reading only as a raw file userkeys.append(generate_key(name, ccn))
self.bzf = file(zinput,'rb') except:
pass
def getlocalname(self, zi): return userkeys
local_header_offset = zi.header_offset
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
leninfo = self.bzf.read(2)
local_name_length, = unpack('<H', leninfo)
self.bzf.seek(local_header_offset + _FILENAME_OFFSET)
local_name = self.bzf.read(local_name_length)
return local_name
def uncompress(self, 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(self, zi):
# get file name length and exta data length to find start of file data
local_header_offset = zi.header_offset
self.bzf.seek(local_header_offset + _FILENAME_LEN_OFFSET)
leninfo = self.bzf.read(2)
local_name_length, = unpack('<H', leninfo)
self.bzf.seek(local_header_offset + _EXTRA_LEN_OFFSET)
exinfo = self.bzf.read(2)
extra_field_length, = unpack('<H', exinfo)
self.bzf.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 == zipfilerugged.ZIP_STORED:
data = self.bzf.read(zi.file_size)
# if compressed we must decompress it using zlib
if zi.compress_type == zipfilerugged.ZIP_DEFLATED:
cmpdata = self.bzf.read(zi.compress_size)
data = self.uncompress(cmpdata)
return data
def fix(self):
# get the zipinfo for each member of the input archive
# and copy member over to output archive
# if problems exist with local vs central filename, fix them
# if epub write mimetype file first, with no compression
if self.ztype == 'epub':
nzinfo = ZipInfo('mimetype', compress_type=zipfilerugged.ZIP_STORED)
self.outzip.writestr(nzinfo, _MIMETYPE)
# write the rest of the files
for zinfo in self.inzip.infolist():
if zinfo.filename != "mimetype" or self.ztype == '.zip':
data = None
nzinfo = zinfo
try:
data = self.inzip.read(zinfo.filename)
except zipfilerugged.BadZipfile or zipfilerugged.error:
local_name = self.getlocalname(zinfo)
data = self.getfiledata(zinfo)
nzinfo.filename = local_name
nzinfo.date_time = zinfo.date_time
nzinfo.compress_type = zinfo.compress_type
nzinfo.flag_bits = 0
nzinfo.internal_attr = 0
self.outzip.writestr(nzinfo,data)
self.bzf.close()
self.inzip.close()
self.outzip.close()
def usage():
print """usage: zipfix.py inputzip outputzip
inputzip is the source zipfile to fix
outputzip is the fixed zip archive
"""
def repairBook(infile, outfile):
if not os.path.exists(infile):
print "Error: Input Zip File does not exist"
return 1
try:
fr = fixZip(infile, outfile)
fr.fix()
return 0
except Exception, e:
print "Error Occurred ", e
return 2
def main(argv=sys.argv):
if len(argv)!=3:
usage()
return 1
infile = argv[1]
outfile = argv[2]
return repairBook(infile, outfile)
if __name__ == '__main__' :
sys.exit(main())

Binary file not shown.

View File

@ -6,8 +6,8 @@ __license__ = 'GPL v3'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
# Released under the terms of the GNU General Public Licence, version 3 or # Released under the terms of the GNU General Public Licence, version 3
# later. <http://www.gnu.org/licenses/> # <http://www.gnu.org/licenses/>
# #
# Requires Calibre version 0.7.55 or higher. # Requires Calibre version 0.7.55 or higher.
# #
@ -58,10 +58,11 @@ __docformat__ = 'restructuredtext en'
# 0.1.8 - Fix for potential problem with PyCrypto # 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.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. # 0.2.0 - Major code change to use unaltered ineptepub.py file 5.8 or later.
# 0.2.1 - Tweaked to eliminate issue with both ignoble and inept calibre plugins installed/enabled at once
PLUGIN_NAME = u"Inept Epub DeDRM" PLUGIN_NAME = u"Inept Epub DeDRM"
PLUGIN_VERSION_TUPLE = (0, 2, 0) PLUGIN_VERSION_TUPLE = (0, 2, 1)
PLUGIN_VERSION = u'.'.join([str(x) for x in PLUGIN_VERSION_TUPLE]) PLUGIN_VERSION = u'.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
import sys, os, re import sys, os, re
@ -118,16 +119,14 @@ class IneptDeDRM(FileTypePlugin):
fr = zipfix.fixZip(path_to_ebook, inf.name) fr = zipfix.fixZip(path_to_ebook, inf.name)
fr.fix() fr.fix()
except Exception, e: except Exception, e:
print u"{0} v{1}: Error when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION) print u"{0} v{1}: Error \'{2}\' when checking zip archive.".format(PLUGIN_NAME, PLUGIN_VERSION, e.args[0])
raise Exception(e) raise Exception(e)
return return
#check the book #check the book
from calibre_plugins.ineptepub import ineptepub from calibre_plugins.ineptepub import ineptepub
if not ineptepub.adeptBook(inf.name): 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)) raise ADEPTError(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. # Load any keyfiles (*.der) included Calibre's config directory.
userkeys = [] userkeys = []
@ -181,30 +180,23 @@ class IneptDeDRM(FileTypePlugin):
# Attempt to decrypt epub with each encryption key found. # Attempt to decrypt epub with each encryption key found.
for userkeyinfo in userkeys: for userkeyinfo in userkeys:
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, userkeyinfo[1]) userkey,keyname = userkeyinfo
print u"{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)
of = self.temporary_file(u".epub") of = self.temporary_file(u".epub")
# Give the user key, ebook and TemporaryPersistent file to the decryption function. # Give the user key, ebook and TemporaryPersistent file to the decryption function.
result = ineptepub.decryptBook(userkeyinfo[0], inf.name, of.name) result = ineptepub.decryptBook(userkey, inf.name, of.name)
# Ebook is not an Adobe Adept epub... do nothing and pass it on. of.close()
# This allows a non-encrypted epub to be imported without error messages.
if result == 1:
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
# Decryption was successful return the modified PersistentTemporary # Decryption was successful return the modified PersistentTemporary
# file to Calibre's import process. # file to Calibre's import process.
if result == 0: if result == 0:
print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION) print u"{0} v{1}: Encryption successfully removed.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close()
return of.name return of.name
break break
print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION) print u"{0} v{1}: Encryption key incorrect.".format(PLUGIN_NAME, PLUGIN_VERSION)
of.close
# Something went wrong with decryption. # Something went wrong with decryption.
# Import the original unmolested epub. # Import the original unmolested epub.

View File

@ -1,4 +1,4 @@
#! /usr/bin/python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
@ -542,7 +542,7 @@ def gui_main():
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception, e:
self.status['text'] = u"Error; {0}".format(e) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = u"File successfully decrypted"

View File

@ -41,7 +41,7 @@ Mac OS X 10.5 and above: You do
\i not \i not
\i0 need to install Python.\ \i0 need to install Python.\
\ \
Drag the DeDRM application from from tools_v5.5.3\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\ Drag the DeDRM application from from tools_v5.6\\DeDRM_Applications\\Macintosh (the location of this ReadMe) to your Applications folder, or anywhere else you find convenient.\
\ \
\ \

View File

@ -24,17 +24,17 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>droplet</string> <string>droplet</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>DeDRM 5.5.3. AppleScript written 20102012 by Apprentice Alf and others.</string> <string>DeDRM 5.6. AppleScript written 20102013 by Apprentice Alf and others.</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>DeDRM</string> <string>DeDRM</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>DeDRM 5.5.3</string> <string>DeDRM 5.6</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.5.3</string> <string>5.6</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>dplt</string> <string>dplt</string>
<key>LSRequiresCarbon</key> <key>LSRequiresCarbon</key>

View File

@ -34,10 +34,14 @@ def _load_libalfcrypto():
else: else:
name_of_lib = 'libalfcrypto64.so' name_of_lib = 'libalfcrypto64.so'
# hard code to local location for libalfcrypto
libalfcrypto = os.path.join(sys.path[0],name_of_lib) libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto): if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found') libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
if not os.path.isfile(libalfcrypto):
libalfcrypto = os.path.join('.',name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found at %s' % libalfcrypto)
libalfcrypto = CDLL(libalfcrypto) libalfcrypto = CDLL(libalfcrypto)

View File

@ -255,13 +255,15 @@ class PageParser(object):
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
'img' : (1, 'snippets', 1, 0), 'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0), 'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0), 'img.y' : (1, 'scalar_number', 0, 0),
'img.h' : (1, 'scalar_number', 0, 0), 'img.h' : (1, 'scalar_number', 0, 0),
'img.w' : (1, 'scalar_number', 0, 0), 'img.w' : (1, 'scalar_number', 0, 0),
'img.src' : (1, 'scalar_number', 0, 0), 'img.src' : (1, 'scalar_number', 0, 0),
'img.color_src' : (1, 'scalar_number', 0, 0), 'img.color_src' : (1, 'scalar_number', 0, 0),
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
'paragraph' : (1, 'snippets', 1, 0), 'paragraph' : (1, 'snippets', 1, 0),
'paragraph.class' : (1, 'scalar_text', 0, 0), 'paragraph.class' : (1, 'scalar_text', 0, 0),
@ -307,6 +309,7 @@ class PageParser(object):
'span.gridEndCenter' : (1, 'scalar_number', 0, 0), 'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.class' : (1, 'scalar_text', 0, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),

View File

@ -387,10 +387,14 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) # we only handle spaceafter word class
if space == '' : space = '0' try:
if (cname == 'spaceafter') and (int(space) > 0) : (cname, space) = argres.split('-',1)
word_class = 'sa' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa'
except:
pass
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))

View File

@ -117,7 +117,7 @@ class Dictionary(object):
self.pos = val self.pos = val
return self.stable[self.pos] return self.stable[self.pos]
else: else:
print "Error - %d outside of string table limits" % val print "Error: %d outside of string table limits" % val
raise TpzDRMError('outside or string table limits') raise TpzDRMError('outside or string table limits')
# sys.exit(-1) # sys.exit(-1)
def getSize(self): def getSize(self):

View File

@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
# ignobleepub.pyw, version 3.6 # ignobleepub.pyw, version 3.7
# Copyright © 2009-2010 by i♥cabbages # Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -26,18 +26,19 @@ from __future__ import with_statement
# 2 - Added OS X support by using OpenSSL when available # 2 - Added OS X support by using OpenSSL when available
# 3 - screen out improper key lengths to prevent segfaults on Linux # 3 - screen out improper key lengths to prevent segfaults on Linux
# 3.1 - Allow Windows versions of libcrypto to be found # 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.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next # 3.3 - On Windows try PyCrypto first, OpenSSL next
# 3.4 - Modify interace to allow use with import # 3.4 - Modify interface to allow use with import
# 3.5 - Fix for potential problem with PyCrypto # 3.5 - Fix for potential problem with PyCrypto
# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code # 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
# 3.7 - Tweaked to match ineptepub more closely
""" """
Decrypt Barnes & Noble encrypted ePub books. Decrypt Barnes & Noble encrypted ePub books.
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "3.6" __version__ = "3.7"
import sys import sys
import os import os
@ -254,18 +255,17 @@ def ignobleBook(inpath):
return True return True
return False return False
# return error code and error message duple
def decryptBook(keyb64, inpath, outpath): def decryptBook(keyb64, inpath, outpath):
if AES is None: if AES is None:
# 1 means don't try again raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
return (1, u"PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16] key = keyb64.decode('base64')[:16]
aes = AES(key) aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf: with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist()) namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \ if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist: 'META-INF/encryption.xml' not in namelist:
return (1, u"Not a secure Barnes & Noble ePub.") print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
return 1
for name in META_NAMES: for name in META_NAMES:
namelist.remove(name) namelist.remove(name)
try: try:
@ -274,7 +274,8 @@ def decryptBook(keyb64, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),) expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr)) bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 64: if len(bookkey) != 64:
return (1, u"Not a secure Barnes & Noble ePub.") print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
return 1
bookkey = aes.decrypt(bookkey.decode('base64')) bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])] bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml') encryption = inf.read('META-INF/encryption.xml')
@ -286,21 +287,23 @@ def decryptBook(keyb64, inpath, outpath):
for path in namelist: for path in namelist:
data = inf.read(path) data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data)) outf.writestr(path, decryptor.decrypt(path, data))
except Exception, e: except:
return (2, u"{0}.".format(e.args[0])) print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
return (0, u"Success") return 2
return 0
def cli_main(argv=unicode_argv()): def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname) print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
print result[1] if result == 0:
return 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(): def gui_main():
import Tkinter import Tkinter
@ -399,10 +402,10 @@ def gui_main():
except Exception, e: except Exception, e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status[0] == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = u"File successfully decrypted"
else: else:
self.status['text'] = decrypt_status[1] self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__)) root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))

View File

@ -1,4 +1,4 @@
#! /usr/bin/python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
@ -542,7 +542,7 @@ def gui_main():
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception, e:
self.status['text'] = u"Error; {0}".format(e) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = u"File successfully decrypted"

View File

@ -50,8 +50,9 @@ from __future__ import with_statement
# 4.7 - Added timing reports, and changed search for Mac key files # 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 # 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. # - Moved back into plugin, __init__ in plugin now only contains plugin code.
# 4.9 - Missed some invalid characters in cleanup_name
__version__ = '4.8' __version__ = '4.9'
import sys, os, re import sys, os, re
@ -144,7 +145,7 @@ def unicode_argv():
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name): def cleanup_name(name):
# substitute filename unfriendly characters # 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"\'") 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"\'").replace(u"*",u"_").replace(u"?",u"")
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime) book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e: except Exception, e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
traceback.print_exc()
return 1 return 1
# if we're saving to the same folder as the original, use file name_ # if we're saving to the same folder as the original, use file name_
@ -246,6 +248,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
book.cleanup() book.cleanup()
return 0
def usage(progname): def usage(progname):

View File

@ -1,13 +1,19 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# DeDRM.pyw, version 5.5.3 # DeDRM.pyw, version 5.6
# By some_updates and Apprentice Alf # By some_updates and Apprentice Alf
import sys import sys
import os, os.path import os, os.path
sys.path.append(os.path.join(sys.path[0],"lib")) sys.path.append(os.path.join(sys.path[0],"lib"))
os.environ['PYTHONIOENCODING'] = "utf-8" import sys, os
import codecs
from argv_utils import add_cp65001_codec, set_utf8_default_encoding, utf8_argv
add_cp65001_codec()
set_utf8_default_encoding()
import shutil import shutil
import Tkinter import Tkinter
@ -16,15 +22,35 @@ import Tkconstants
import tkFileDialog import tkFileDialog
from scrolltextwidget import ScrolledText from scrolltextwidget import ScrolledText
from activitybar import ActivityBar from activitybar import ActivityBar
import subprocess
from subprocess import Popen, PIPE, STDOUT
import subasyncio
from subasyncio import Process
import re import re
import simpleprefs import simpleprefs
from Queue import Full
from Queue import Empty
from multiprocessing import Process, Queue
__version__ = '5.5.3' from scriptinterface import decryptepub, decryptpdb, decryptpdf, decryptk4mobi
# Wrap a stream so that output gets flushed immediately
# and appended to shared queue
class QueuedStream:
def __init__(self, stream, q):
self.stream = stream
self.encoding = stream.encoding
self.q = q
if self.encoding == None:
self.encoding = "utf-8"
def write(self, data):
if isinstance(data,unicode):
data = data.encode(self.encoding,"replace")
self.q.put(data)
# self.stream.write(data)
# self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)
__version__ = '5.6'
class DrmException(Exception): class DrmException(Exception):
pass pass
@ -35,6 +61,7 @@ class MainApp(Tk):
self.withdraw() self.withdraw()
self.dnd = dnd self.dnd = dnd
self.apphome = apphome self.apphome = apphome
# preference settings # preference settings
# [dictionary key, file in preferences directory where info is stored] # [dictionary key, file in preferences directory where info is stored]
description = [ ['pids' , 'pidlist.txt' ], description = [ ['pids' , 'pidlist.txt' ],
@ -152,7 +179,7 @@ class PrefsDialog(Toplevel):
self.pidnums.set(self.prefs_array['pids']) self.pidnums.set(self.prefs_array['pids'])
self.pidinfo.grid(row=3, column=1, sticky=sticky) self.pidinfo.grid(row=3, column=1, sticky=sticky)
Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, first character B, comma separated)').grid(row=4, sticky=Tkconstants.E) Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=4, sticky=Tkconstants.E)
self.sernums = Tkinter.StringVar() self.sernums = Tkinter.StringVar()
self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums) self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums)
if 'serials' in self.prefs_array: if 'serials' in self.prefs_array:
@ -327,10 +354,11 @@ class ConvDialog(Toplevel):
self.filenames = filenames self.filenames = filenames
self.interval = 50 self.interval = 50
self.p2 = None self.p2 = None
self.q = Queue()
self.running = 'inactive' self.running = 'inactive'
self.numgood = 0 self.numgood = 0
self.numbad = 0 self.numbad = 0
self.log = u"" self.log = ''
self.status = Tkinter.Label(self, text='DeDRM processing...') self.status = Tkinter.Label(self, text='DeDRM processing...')
self.status.pack(fill=Tkconstants.X, expand=1) self.status.pack(fill=Tkconstants.X, expand=1)
body = Tkinter.Frame(self) body = Tkinter.Frame(self)
@ -378,16 +406,18 @@ class ConvDialog(Toplevel):
if len(self.filenames) > 0: if len(self.filenames) > 0:
filename = self.filenames.pop(0) filename = self.filenames.pop(0)
if filename == None: if filename == None:
msg = u"\nComplete: Successes: {0}, Failures: {1}\n".format(self.numgood,self.numbad) msg = '\nComplete: '
msg += 'Successes: %d, ' % self.numgood
msg += 'Failures: %d\n' % self.numbad
self.showCmdOutput(msg) self.showCmdOutput(msg)
if self.numbad == 0: if self.numbad == 0:
self.after(2000,self.conversion_done()) self.after(2000,self.conversion_done())
logfile = os.path.join(rscpath,'dedrm.log') logfile = os.path.join(rscpath,'dedrm.log')
file(logfile,'w').write(self.log.encode('utf8')) file(logfile,'wb').write(self.log)
return return
infile = filename infile = filename
bname = os.path.basename(infile) bname = os.path.basename(infile)
msg = u"Processing: {0} ... ".format(bname) msg = 'Processing: ' + bname + ' ... '
self.log += msg self.log += msg
self.showCmdOutput(msg) self.showCmdOutput(msg)
outdir = os.path.dirname(filename) outdir = os.path.dirname(filename)
@ -399,9 +429,9 @@ class ConvDialog(Toplevel):
if rv == 0: if rv == 0:
self.bar.start() self.bar.start()
self.running = 'active' self.running = 'active'
self.processPipe() self.processQueue()
else: else:
msg = u"Unknown File: {0}\n".format(bname) msg = 'Unknown File: ' + bname + '\n'
self.log += msg self.log += msg
self.showCmdOutput(msg) self.showCmdOutput(msg)
self.numbad += 1 self.numbad += 1
@ -410,7 +440,7 @@ class ConvDialog(Toplevel):
# kill any still running subprocess # kill any still running subprocess
self.running = 'stopped' self.running = 'stopped'
if self.p2 != None: if self.p2 != None:
if (self.p2.wait('nowait') == None): if (self.p2.exitcode == None):
self.p2.terminate() self.p2.terminate()
self.conversion_done() self.conversion_done()
@ -426,130 +456,127 @@ class ConvDialog(Toplevel):
# read from subprocess pipe without blocking # read from subprocess pipe without blocking
# invoked every interval via the widget "after" # invoked every interval via the widget "after"
# option being used, so need to reset it for the next time # option being used, so need to reset it for the next time
def processPipe(self): def processQueue(self):
if self.p2 == None: if self.p2 == None:
# nothing to wait for so just return # nothing to wait for so just return
return return
poll = self.p2.wait('nowait') poll = self.p2.exitcode
if poll != None: if poll != None:
self.bar.stop() self.bar.stop()
if poll == 0: if poll == 0:
msg = u"\nSuccess\n" msg = 'Success\n'
self.numgood += 1 self.numgood += 1
text = self.p2.read().decode('utf8') done = False
text += self.p2.readerr().decode('utf8') text = ''
while not done:
try:
data = self.q.get_nowait()
text += data
except Empty:
done = True
pass
self.log += text self.log += text
self.log += msg self.log += msg
else: if poll != 0:
msg = u"\nFailed\n" msg = 'Failed\n'
text = self.p2.read().decode('utf8') done = False
text += self.p2.readerr().decode('utf8') text = ''
msg += text while not done:
self.numbad += 1 try:
data = self.q.get_nowait()
text += data
except Empty:
done = True
pass
msg += '\n'
self.log += text
self.log += msg self.log += msg
self.numbad += 1
self.p2.join()
self.showCmdOutput(msg) self.showCmdOutput(msg)
self.p2 = None self.p2 = None
self.running = 'inactive' self.running = 'inactive'
self.after(50,self.processBooks) self.after(50,self.processBooks)
return return
try:
text = self.q.get_nowait()
except Empty:
text = ''
pass
if text != '':
self.log += text
# make sure we get invoked again by event loop after interval # make sure we get invoked again by event loop after interval
self.stext.after(self.interval,self.processPipe) self.stext.after(self.interval,self.processQueue)
return return
def decrypt_ebook(self, infile, outdir, rscpath): def decrypt_ebook(self, infile, outdir, rscpath):
apphome = self.apphome q = self.q
rv = 1 rv = 1
name, ext = os.path.splitext(os.path.basename(infile)) name, ext = os.path.splitext(os.path.basename(infile))
ext = ext.lower() ext = ext.lower()
if ext == '.epub': if ext == '.epub':
self.p2 = processEPUB(apphome, infile, outdir, rscpath) self.p2 = Process(target=processEPUB, args=(q, infile, outdir, rscpath))
self.p2.start()
return 0 return 0
if ext == '.pdb': if ext == '.pdb':
self.p2 = processPDB(apphome, infile, outdir, rscpath) self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath))
self.p2.start()
return 0 return 0
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']: if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']:
self.p2 = processK4MOBI(apphome, infile, outdir, rscpath) self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath))
self.p2.start()
return 0 return 0
if ext == '.pdf': if ext == '.pdf':
self.p2 = processPDF(apphome, infile, outdir, rscpath) self.p2 = Process(target=processPDF, args=(q, infile, outdir, rscpath))
self.p2.start()
return 0 return 0
return rv return rv
# run as a subprocess via pipes and collect stdout, stderr, and return value # child process starts here
def runit(apphome, ncmd, nparms): def processK4MOBI(q, infile, outdir, rscpath):
pengine = sys.executable add_cp65001_codec()
if pengine is None or pengine == '': set_utf8_default_encoding()
pengine = 'python' sys.stdout = QueuedStream(sys.stdout, q)
pengine = os.path.normpath(pengine) sys.stderr = QueuedStream(sys.stderr, q)
cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" ' rv = decryptk4mobi(infile, outdir, rscpath)
# if sys.platform.startswith('win'): sys.exit(rv)
# search_path = os.environ['PATH']
# search_path = search_path.lower()
# if search_path.find('python') < 0:
# # if no python hope that win registry finds what is associated with py extension
# cmdline = pengine + ' "' + os.path.join(apphome, ncmd) + '" '
cmdline += nparms
cmdline = cmdline.encode(sys.getfilesystemencoding())
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): # child process starts here
cmd = os.path.join('lib','k4mobidedrm.py') def processPDF(q, infile, outdir, rscpath):
parms = '' add_cp65001_codec()
pidnums = '' set_utf8_default_encoding()
pidspath = os.path.join(rscpath,'pidlist.txt') sys.stdout = QueuedStream(sys.stdout, q)
if os.path.exists(pidspath): sys.stderr = QueuedStream(sys.stderr, q)
pidnums = file(pidspath,'r').read() rv = decryptpdf(infile, outdir, rscpath)
pidnums = pidnums.rstrip(os.linesep) sys.exit(rv)
if pidnums != '':
parms += '-p "' + pidnums + '" '
serialnums = ''
serialnumspath = os.path.join(rscpath,'seriallist.txt')
if os.path.exists(serialnumspath):
serialnums = file(serialnumspath,'r').read()
serialnums = serialnums.rstrip(os.linesep)
if serialnums != '':
parms += '-s "' + serialnums + '" '
files = os.listdir(rscpath) # child process starts here
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE) def processEPUB(q, infile, outdir, rscpath):
files = filter(filefilter.search, files) add_cp65001_codec()
if files: set_utf8_default_encoding()
for filename in files: sys.stdout = QueuedStream(sys.stdout, q)
dpath = os.path.join(rscpath,filename) sys.stderr = QueuedStream(sys.stderr, q)
parms += '-k "' + dpath + '" ' rv = decryptepub(infile, outdir, rscpath)
parms += '"' + infile +'" "' + outdir + '"' sys.exit(rv)
p2 = runit(apphome, cmd, parms)
return p2
def processPDF(apphome, infile, outdir, rscpath): # child process starts here
cmd = os.path.join('lib','decryptpdf.py') def processPDB(q, infile, outdir, rscpath):
parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"' add_cp65001_codec()
p2 = runit(apphome, cmd, parms) set_utf8_default_encoding()
return p2 sys.stdout = QueuedStream(sys.stdout, q)
sys.stderr = QueuedStream(sys.stderr, q)
def processEPUB(apphome, infile, outdir, rscpath): rv = decryptpdb(infile, outdir, rscpath)
# invoke routine to check both Adept and Barnes and Noble sys.exit(rv)
cmd = os.path.join('lib','decryptepub.py')
parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
p2 = runit(apphome, cmd, parms)
return p2
def processPDB(apphome, infile, outdir, rscpath):
cmd = os.path.join('lib','decryptpdb.py')
parms = '"' + infile + '" "' + outdir + '" "' + rscpath + '"'
p2 = runit(apphome, cmd, parms)
return p2
def main(argv=sys.argv): def main(argv=utf8_argv()):
apphome = os.path.dirname(sys.argv[0]) apphome = os.path.dirname(argv[0])
apphome = os.path.abspath(apphome) apphome = os.path.abspath(apphome)
# windows may pass a spurious quoted null string as argv[1] from bat file # windows may pass a spurious quoted null string as argv[1] from bat file
# simply work around this until we can figure out a better way to handle things # simply work around this until we can figure out a better way to handle things
if len(argv) == 2: if sys.platform.startswith('win') and len(argv) == 2:
temp = argv[1] temp = argv[1]
temp = temp.strip('"') temp = temp.strip('"')
temp = temp.strip() temp = temp.strip()
@ -563,11 +590,10 @@ def main(argv=sys.argv):
else : # processing books via drag and drop else : # processing books via drag and drop
dnd = True dnd = True
# build a list of the files to be processed # build a list of the files to be processed
# note all filenames and paths have been utf-8 encoded
infilelst = argv[1:] infilelst = argv[1:]
filenames = [] filenames = []
for infile in infilelst: for infile in infilelst:
infile = infile.decode(sys.getfilesystemencoding())
print infile
infile = infile.replace('"','') infile = infile.replace('"','')
infile = os.path.abspath(infile) infile = os.path.abspath(infile)
if os.path.isdir(infile): if os.path.isdir(infile):

View File

@ -34,10 +34,14 @@ def _load_libalfcrypto():
else: else:
name_of_lib = 'libalfcrypto64.so' name_of_lib = 'libalfcrypto64.so'
# hard code to local location for libalfcrypto
libalfcrypto = os.path.join(sys.path[0],name_of_lib) libalfcrypto = os.path.join(sys.path[0],name_of_lib)
if not os.path.isfile(libalfcrypto): if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found') libalfcrypto = os.path.join(sys.path[0], 'lib', name_of_lib)
if not os.path.isfile(libalfcrypto):
libalfcrypto = os.path.join('.',name_of_lib)
if not os.path.isfile(libalfcrypto):
raise Exception('libalfcrypto not found at %s' % libalfcrypto)
libalfcrypto = CDLL(libalfcrypto) libalfcrypto = CDLL(libalfcrypto)

View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys, os
import locale
import codecs
# get sys.argv arguments and encode them into utf-8
def utf8_argv():
if sys.platform.startswith('win'):
# Versions 2.x of Python don't support Unicode in sys.argv on
# Windows, with the underlying Windows API instead replacing multi-byte
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
# as a list of Unicode strings and encode them as utf-8
from ctypes import POINTER, byref, cdll, c_int, windll
from ctypes.wintypes import LPCWSTR, LPWSTR
GetCommandLineW = cdll.kernel32.GetCommandLineW
GetCommandLineW.argtypes = []
GetCommandLineW.restype = LPCWSTR
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
CommandLineToArgvW.restype = POINTER(LPWSTR)
cmd = GetCommandLineW()
argc = c_int(0)
argv = CommandLineToArgvW(cmd, byref(argc))
if argc.value > 0:
# Remove Python executable and commands if present
start = argc.value - len(sys.argv)
return [argv[i].encode('utf-8') for i in
xrange(start, argc.value)]
# this should never happen
return None
else:
argv = []
argvencoding = sys.stdin.encoding
if argvencoding == None:
argvencoding = sys.getfilesystemencoding()
if argvencoding == None:
argvencoding = 'utf-8'
for arg in sys.argv:
if type(arg) == unicode:
argv.append(arg.encode('utf-8'))
else:
argv.append(arg.decode(argvencoding).encode('utf-8'))
return argv
def add_cp65001_codec():
try:
codecs.lookup('cp65001')
except LookupError:
codecs.register(
lambda name: name == 'cp65001' and codecs.lookup('utf-8') or None)
return
def set_utf8_default_encoding():
if sys.getdefaultencoding() == 'utf-8':
return
# Regenerate setdefaultencoding.
reload(sys)
sys.setdefaultencoding('utf-8')
for attr in dir(locale):
if attr[0:3] != 'LC_':
continue
aref = getattr(locale, attr)
try:
locale.setlocale(aref, '')
except locale.Error:
continue
try:
lang = locale.getlocale(aref)[0]
except (TypeError, ValueError):
continue
if lang:
try:
locale.setlocale(aref, (lang, 'UTF-8'))
except locale.Error:
os.environ[attr] = lang + '.UTF-8'
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass
return

View File

@ -255,13 +255,15 @@ class PageParser(object):
'empty_text_region' : (1, 'snippets', 1, 0), 'empty_text_region' : (1, 'snippets', 1, 0),
'img' : (1, 'snippets', 1, 0), 'img' : (1, 'snippets', 1, 0),
'img.x' : (1, 'scalar_number', 0, 0), 'img.x' : (1, 'scalar_number', 0, 0),
'img.y' : (1, 'scalar_number', 0, 0), 'img.y' : (1, 'scalar_number', 0, 0),
'img.h' : (1, 'scalar_number', 0, 0), 'img.h' : (1, 'scalar_number', 0, 0),
'img.w' : (1, 'scalar_number', 0, 0), 'img.w' : (1, 'scalar_number', 0, 0),
'img.src' : (1, 'scalar_number', 0, 0), 'img.src' : (1, 'scalar_number', 0, 0),
'img.color_src' : (1, 'scalar_number', 0, 0), 'img.color_src' : (1, 'scalar_number', 0, 0),
'img.gridBeginCenter' : (1, 'scalar_number', 0, 0),
'img.gridEndCenter' : (1, 'scalar_number', 0, 0),
'paragraph' : (1, 'snippets', 1, 0), 'paragraph' : (1, 'snippets', 1, 0),
'paragraph.class' : (1, 'scalar_text', 0, 0), 'paragraph.class' : (1, 'scalar_text', 0, 0),
@ -307,6 +309,7 @@ class PageParser(object):
'span.gridEndCenter' : (1, 'scalar_number', 0, 0), 'span.gridEndCenter' : (1, 'scalar_number', 0, 0),
'extratokens' : (1, 'snippets', 1, 0), 'extratokens' : (1, 'snippets', 1, 0),
'extratokens.class' : (1, 'scalar_text', 0, 0),
'extratokens.type' : (1, 'scalar_text', 0, 0), 'extratokens.type' : (1, 'scalar_text', 0, 0),
'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.firstGlyph' : (1, 'scalar_number', 0, 0),
'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0), 'extratokens.lastGlyph' : (1, 'scalar_number', 0, 0),

View File

@ -1,88 +0,0 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
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
import ineptepub
import ignobleepub
import zipfix
import re
def main(argv=sys.argv):
args = argv[1:]
if len(args) != 3:
return -1
infile = args[0]
outdir = args[1]
rscpath = args[2]
errlog = ''
# first fix the epub to make sure we do not get errors
name, ext = os.path.splitext(os.path.basename(infile))
bpath = os.path.dirname(infile)
zippath = os.path.join(bpath,name + '_temp.zip')
rv = zipfix.repairBook(infile, zippath)
if rv != 0:
print "Error while trying to fix epub"
return rv
# determine a good name for the output file
outfile = os.path.join(outdir, name + '_nodrm.epub')
rv = 1
# first try with the Adobe adept epub
# try with any keyfiles (*.der) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.der$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
try:
rv = ineptepub.decryptBook(keypath, zippath, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
if rv == 0:
os.remove(zippath)
return 0
# still no luck
# now try with ignoble epub
# try with any keyfiles (*.b64) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.b64$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
try:
rv = ignobleepub.decryptBook(keypath, zippath, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
os.remove(zippath)
if rv != 0:
print errlog
return rv
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
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
import erdr2pml
def main(argv=sys.argv):
args = argv[1:]
if len(args) != 3:
return -1
infile = args[0]
outdir = args[1]
rscpath = args[2]
rv = 1
socialpath = os.path.join(rscpath,'sdrmlist.txt')
if os.path.exists(socialpath):
keydata = file(socialpath,'r').read()
keydata = keydata.rstrip(os.linesep)
ar = keydata.split(',')
for i in ar:
try:
name, cc8 = i.split(':')
except ValueError:
print ' Error parsing user supplied social drm data.'
return 1
rv = erdr2pml.decryptBook(infile, outdir, True, erdr2pml.getuser_key(name, cc8) )
if rv == 0:
break
return rv
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,54 +0,0 @@
#!/usr/bin/env python
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
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
import re
import ineptpdf
def main(argv=sys.argv):
args = argv[1:]
if len(args) != 3:
return -1
infile = args[0]
outdir = args[1]
rscpath = args[2]
errlog = ''
rv = 1
# determine a good name for the output file
name, ext = os.path.splitext(os.path.basename(infile))
outfile = os.path.join(outdir, name + '_nodrm.pdf')
# try with any keyfiles (*.der) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.der$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
try:
rv = ineptpdf.decryptBook(keypath, infile, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
if rv != 0:
print errlog
return rv
if __name__ == "__main__":
sys.exit(main())

View File

@ -387,10 +387,14 @@ class DocParser(object):
ws_last = int(argres) ws_last = int(argres)
elif name.endswith('word.class'): elif name.endswith('word.class'):
(cname, space) = argres.split('-',1) # we only handle spaceafter word class
if space == '' : space = '0' try:
if (cname == 'spaceafter') and (int(space) > 0) : (cname, space) = argres.split('-',1)
word_class = 'sa' if space == '' : space = '0'
if (cname == 'spaceafter') and (int(space) > 0) :
word_class = 'sa'
except:
pass
elif name.endswith('word.img.src'): elif name.endswith('word.img.src'):
result.append(('img' + word_class, int(argres))) result.append(('img' + word_class, int(argres)))

View File

@ -117,7 +117,7 @@ class Dictionary(object):
self.pos = val self.pos = val
return self.stable[self.pos] return self.stable[self.pos]
else: else:
print "Error - %d outside of string table limits" % val print "Error: %d outside of string table limits" % val
raise TpzDRMError('outside or string table limits') raise TpzDRMError('outside or string table limits')
# sys.exit(-1) # sys.exit(-1)
def getSize(self): def getSize(self):

View File

@ -3,7 +3,7 @@
from __future__ import with_statement from __future__ import with_statement
# ignobleepub.pyw, version 3.6 # ignobleepub.pyw, version 3.7
# Copyright © 2009-2010 by i♥cabbages # Copyright © 2009-2010 by i♥cabbages
# Released under the terms of the GNU General Public Licence, version 3 # Released under the terms of the GNU General Public Licence, version 3
@ -26,18 +26,19 @@ from __future__ import with_statement
# 2 - Added OS X support by using OpenSSL when available # 2 - Added OS X support by using OpenSSL when available
# 3 - screen out improper key lengths to prevent segfaults on Linux # 3 - screen out improper key lengths to prevent segfaults on Linux
# 3.1 - Allow Windows versions of libcrypto to be found # 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.2 - add support for encoding to 'utf-8' when building up list of files to decrypt from encryption.xml
# 3.3 - On Windows try PyCrypto first and OpenSSL next # 3.3 - On Windows try PyCrypto first, OpenSSL next
# 3.4 - Modify interace to allow use with import # 3.4 - Modify interface to allow use with import
# 3.5 - Fix for potential problem with PyCrypto # 3.5 - Fix for potential problem with PyCrypto
# 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code # 3.6 - Revised to allow use in calibre plugins to eliminate need for duplicate code
# 3.7 - Tweaked to match ineptepub more closely
""" """
Decrypt Barnes & Noble encrypted ePub books. Decrypt Barnes & Noble encrypted ePub books.
""" """
__license__ = 'GPL v3' __license__ = 'GPL v3'
__version__ = "3.6" __version__ = "3.7"
import sys import sys
import os import os
@ -254,18 +255,17 @@ def ignobleBook(inpath):
return True return True
return False return False
# return error code and error message duple
def decryptBook(keyb64, inpath, outpath): def decryptBook(keyb64, inpath, outpath):
if AES is None: if AES is None:
# 1 means don't try again raise IGNOBLEError(u"PyCrypto or OpenSSL must be installed.")
return (1, u"PyCrypto or OpenSSL must be installed.")
key = keyb64.decode('base64')[:16] key = keyb64.decode('base64')[:16]
aes = AES(key) aes = AES(key)
with closing(ZipFile(open(inpath, 'rb'))) as inf: with closing(ZipFile(open(inpath, 'rb'))) as inf:
namelist = set(inf.namelist()) namelist = set(inf.namelist())
if 'META-INF/rights.xml' not in namelist or \ if 'META-INF/rights.xml' not in namelist or \
'META-INF/encryption.xml' not in namelist: 'META-INF/encryption.xml' not in namelist:
return (1, u"Not a secure Barnes & Noble ePub.") print u"{0:s} is DRM-free.".format(os.path.basename(inpath))
return 1
for name in META_NAMES: for name in META_NAMES:
namelist.remove(name) namelist.remove(name)
try: try:
@ -274,7 +274,8 @@ def decryptBook(keyb64, inpath, outpath):
expr = './/%s' % (adept('encryptedKey'),) expr = './/%s' % (adept('encryptedKey'),)
bookkey = ''.join(rights.findtext(expr)) bookkey = ''.join(rights.findtext(expr))
if len(bookkey) != 64: if len(bookkey) != 64:
return (1, u"Not a secure Barnes & Noble ePub.") print u"{0:s} is not a secure Barnes & Noble ePub.".format(os.path.basename(inpath))
return 1
bookkey = aes.decrypt(bookkey.decode('base64')) bookkey = aes.decrypt(bookkey.decode('base64'))
bookkey = bookkey[:-ord(bookkey[-1])] bookkey = bookkey[:-ord(bookkey[-1])]
encryption = inf.read('META-INF/encryption.xml') encryption = inf.read('META-INF/encryption.xml')
@ -286,21 +287,23 @@ def decryptBook(keyb64, inpath, outpath):
for path in namelist: for path in namelist:
data = inf.read(path) data = inf.read(path)
outf.writestr(path, decryptor.decrypt(path, data)) outf.writestr(path, decryptor.decrypt(path, data))
except Exception, e: except:
return (2, u"{0}.".format(e.args[0])) print u"Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc())
return (0, u"Success") return 2
return 0
def cli_main(argv=unicode_argv()): def cli_main(argv=unicode_argv()):
progname = os.path.basename(argv[0]) progname = os.path.basename(argv[0])
if len(argv) != 4: if len(argv) != 4:
print u"usage: {0} <keyfile.der> <inbook.epub> <outbook.epub>".format(progname) print u"usage: {0} <keyfile.b64> <inbook.epub> <outbook.epub>".format(progname)
return 1 return 1
keypath, inpath, outpath = argv[1:] keypath, inpath, outpath = argv[1:]
userkey = open(keypath,'rb').read() userkey = open(keypath,'rb').read()
result = decryptBook(userkey, inpath, outpath) result = decryptBook(userkey, inpath, outpath)
print result[1] if result == 0:
return 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(): def gui_main():
import Tkinter import Tkinter
@ -399,10 +402,10 @@ def gui_main():
except Exception, e: except Exception, e:
self.status['text'] = u"Error: {0}".format(e.args[0]) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status[0] == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = u"File successfully decrypted"
else: else:
self.status['text'] = decrypt_status[1] self.status['text'] = u"The was an error decrypting the file."
root = Tkinter.Tk() root = Tkinter.Tk()
root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__)) root.title(u"Barnes & Noble ePub Decrypter v.{0}".format(__version__))

View File

@ -1,4 +1,4 @@
#! /usr/bin/python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import with_statement from __future__ import with_statement
@ -542,7 +542,7 @@ def gui_main():
try: try:
decrypt_status = decryptBook(userkey, inpath, outpath) decrypt_status = decryptBook(userkey, inpath, outpath)
except Exception, e: except Exception, e:
self.status['text'] = u"Error; {0}".format(e) self.status['text'] = u"Error: {0}".format(e.args[0])
return return
if decrypt_status == 0: if decrypt_status == 0:
self.status['text'] = u"File successfully decrypted" self.status['text'] = u"File successfully decrypted"

View File

@ -50,8 +50,9 @@ from __future__ import with_statement
# 4.7 - Added timing reports, and changed search for Mac key files # 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 # 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. # - Moved back into plugin, __init__ in plugin now only contains plugin code.
# 4.9 - Missed some invalid characters in cleanup_name
__version__ = '4.8' __version__ = '4.9'
import sys, os, re import sys, os, re
@ -144,7 +145,7 @@ def unicode_argv():
# and with some (heavily edited) code from Paul Durrant's kindlenamer.py # and with some (heavily edited) code from Paul Durrant's kindlenamer.py
def cleanup_name(name): def cleanup_name(name):
# substitute filename unfriendly characters # 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"\'") 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"\'").replace(u"*",u"_").replace(u"?",u"")
# delete control characters # delete control characters
name = u"".join(char for char in name if ord(char)>=32) name = u"".join(char for char in name if ord(char)>=32)
# white space to single space, delete leading and trailing while space # white space to single space, delete leading and trailing while space
@ -220,6 +221,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime) book = GetDecryptedBook(infile, kInfoFiles, serials, pids, starttime)
except Exception, e: except Exception, e:
print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime) print u"Error decrypting book after {1:.1f} seconds: {0}".format(e.args[0],time.time()-starttime)
traceback.print_exc()
return 1 return 1
# if we're saving to the same folder as the original, use file name_ # if we're saving to the same folder as the original, use file name_
@ -246,6 +248,7 @@ def decryptBook(infile, outdir, kInfoFiles, serials, pids):
# remove internal temporary directory of Topaz pieces # remove internal temporary directory of Topaz pieces
book.cleanup() book.cleanup()
return 0
def usage(progname): def usage(progname):

View File

@ -0,0 +1,153 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
import sys
import os
import re
import ineptepub
import ignobleepub
import zipfix
import ineptpdf
import erdr2pml
import k4mobidedrm
def decryptepub(infile, outdir, rscpath):
errlog = ''
# first fix the epub to make sure we do not get errors
name, ext = os.path.splitext(os.path.basename(infile))
bpath = os.path.dirname(infile)
zippath = os.path.join(bpath,name + '_temp.zip')
rv = zipfix.repairBook(infile, zippath)
if rv != 0:
print "Error while trying to fix epub"
return rv
# determine a good name for the output file
outfile = os.path.join(outdir, name + '_nodrm.epub')
rv = 1
# first try with the Adobe adept epub
# try with any keyfiles (*.der) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.der$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
userkey = open(keypath,'rb').read()
try:
rv = ineptepub.decryptBook(userkey, zippath, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
if rv == 0:
os.remove(zippath)
return 0
# still no luck
# now try with ignoble epub
# try with any keyfiles (*.b64) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.b64$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
userkey = open(keypath,'rb').read()
try:
rv = ignobleepub.decryptBook(userkey, zippath, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
os.remove(zippath)
if rv != 0:
print errlog
return rv
def decryptpdf(infile, outdir, rscpath):
errlog = ''
rv = 1
# determine a good name for the output file
name, ext = os.path.splitext(os.path.basename(infile))
outfile = os.path.join(outdir, name + '_nodrm.pdf')
# try with any keyfiles (*.der) in the rscpath
files = os.listdir(rscpath)
filefilter = re.compile("\.der$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
keypath = os.path.join(rscpath, filename)
userkey = open(keypath,'rb').read()
try:
rv = ineptpdf.decryptBook(userkey, infile, outfile)
if rv == 0:
break
except Exception, e:
errlog += str(e)
rv = 1
pass
if rv != 0:
print errlog
return rv
def decryptpdb(infile, outdir, rscpath):
outname = os.path.splitext(os.path.basename(infile))[0] + ".pmlz"
outpath = os.path.join(outdir, outname)
rv = 1
socialpath = os.path.join(rscpath,'sdrmlist.txt')
if os.path.exists(socialpath):
keydata = file(socialpath,'r').read()
keydata = keydata.rstrip(os.linesep)
ar = keydata.split(',')
for i in ar:
try:
name, cc8 = i.split(':')
except ValueError:
print ' Error parsing user supplied social drm data.'
return 1
rv = erdr2pml.decryptBook(infile, outpath, True, erdr2pml.getuser_key(name, cc8))
if rv == 0:
break
return rv
def decryptk4mobi(infile, outdir, rscpath):
rv = 1
pidnums = []
pidspath = os.path.join(rscpath,'pidlist.txt')
if os.path.exists(pidspath):
pidstr = file(pidspath,'r').read()
pidstr = pidstr.rstrip(os.linesep)
pidstr = pidstr.strip()
if pidstr != '':
pidnums = pidstr.split(',')
serialnums = []
serialnumspath = os.path.join(rscpath,'seriallist.txt')
if os.path.exists(serialnumspath):
serialstr = file(serialnumspath,'r').read()
serialstr = serialstr.rstrip(os.linesep)
serialstr = serialstr.strip()
if serialstr != '':
serialnums = serialstr.split(',')
kInfoFiles = []
files = os.listdir(rscpath)
filefilter = re.compile("\.info$|\.kinf$", re.IGNORECASE)
files = filter(filefilter.search, files)
if files:
for filename in files:
dpath = os.path.join(rscpath,filename)
kInfoFiles.append(dpath)
rv = k4mobidedrm.decryptBook(infile, outdir, kInfoFiles, serialnums, pidnums)
return rv

View File

@ -1,7 +1,7 @@
ReadMe_DeDRM_v5.5.3_WinApp ReadMe_DeDRM_v5.6_WinApp
======================== ========================
DeDRM_v5.5.3_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages all the "tools" python software in one easy to use program that remembers preferences and settings. DeDRM_v5.6_WinApp is a pure python drag and drop application that allows users to drag and drop ebooks or folders of ebooks onto the DeDRM_Drop_Target to have the DRM removed. It repackages all the "tools" python software in one easy to use program that remembers preferences and settings.
It will work without manual configuration for Kindle for PC ebooks and Adobe Adept epub and pdf ebooks. It will work without manual configuration for Kindle for PC ebooks and Adobe Adept epub and pdf ebooks.
@ -23,9 +23,9 @@ Installation
0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing. 0. If you don't already have a correct version of Python and PyCrypto installed, follow the "Installing Python on Windows" and "Installing PyCrypto on Windows" sections below before continuing.
1. Drag the DeDRM_5.5.3 folder from tools_v5.5.3/DeDRM_Applications/Windows to your "My Documents" folder. 1. Drag the DeDRM_5.6 folder from tools_v5.6/DeDRM_Applications/Windows to your "My Documents" folder.
2. Open the DeDRM_5.5.3 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop. 2. Open the DeDRM_5.6 folder you've just dragged, and make a short-cut of the DeDRM_Drop_Target.bat file (right-click/Create Shortcut). Drag the shortcut file onto your Desktop.
3. To set the preferences simply double-click on your just created short-cut. 3. To set the preferences simply double-click on your just created short-cut.

View File

@ -9,7 +9,7 @@ If the downloaded file is encrypted, install and configure the ignoble plugin in
DOWNLOAD HIDDEN FILES FROM B&N DOWNLOAD HIDDEN FILES FROM B&N
------------------------------ ------------------------------
Some content is not downloadable from the B&N website, notably magazines. The Greasemonkey script included in the tools modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website. Some content is not downloadable from the B&N website, notably magazines. A Greasemonkey script (link below) modifies the myNook page of the Barnes and Noble website to show a download button for normally non-downloadable content. This will work until Barnes & Noble changes their website.
Prerequisites Prerequisites
------------- -------------

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
ineptpdf 8.4.51
---------------
This is a version of the ineptpdf script produced by TetraChroma that can remove, on Windows, "FileOpen" DRM.
No support for this script is offered at Apprentice Alf's blog.
Trtrachroma's blog is http://tetrachroma.wordpress.com/

View File

@ -1,7 +1,7 @@
Welcome to the tools! Welcome to the tools!
===================== =====================
This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.5.3 archive. This ReadMe_First.txt is meant to give users a quick overview of what is available and how to get started. This document is part of the Tools v5.6 archive.
The is archive includes tools to remove DRM from: The is archive includes tools to remove DRM from:
@ -51,7 +51,7 @@ DeDRM application for Mac OS X users: (Mac OS X 10.4 and above)
---------------------------------------------------------------------- ----------------------------------------------------------------------
This application combines all the tools into one easy-to-use tool for Mac OS X users. This application combines all the tools into one easy-to-use tool for Mac OS X users.
Drag the "DeDRM 5.5.3.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe. Drag the "DeDRM 5.6.app" application from the DeDRM_Applications/Macintosh folder to your Desktop (or your Applications Folder, or anywhere else you find convenient). Double-click on the application to run it and it will guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above. To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM application and it will remove the DRM of the kinds listed above.
@ -67,7 +67,7 @@ DeDRM application for Windows users: (Windows XP through Windows 8)
This application combines all the tools into one easy-to-use tool for Windows users. This application combines all the tools into one easy-to-use tool for Windows users.
Drag the DeDRM_5.5.3 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.5.3 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe. Drag the DeDRM_5.6 folder that's in the DeDRM_Applications/Windows folder, to your "My Documents" folder (or anywhere else you find convenient). Make a short-cut on your Desktop of the DeDRM_Drop_Target.bat file that's in the DeDRM_5.6 folder. Double-click on the shortcut and the DeDRM application will run and guide you through collecting the data it needs to remove the DRM from any of the kinds of DRMed ebook listed in the first section of this ReadMe.
To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above. To use the DeDRM application, simply drag ebooks, or folders containing ebooks, onto the DeDRM_Drop_Target.bat shortcut and it will remove the DRM of the kinds listed above.