mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 21:08:57 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a8d5f74a6 | ||
|
|
e729ae8904 |
Binary file not shown.
@@ -24,7 +24,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>DeDRM AppleScript 6.2.0. Written 2010–2015 by Apprentice Alf et al.</string>
|
||||
<string>DeDRM AppleScript 6.2.2 Written 2010–2015 by Apprentice Alf et al.</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>DeDRM</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -36,13 +36,13 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.2.0</string>
|
||||
<string>6.2.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2010–2015 Paul Durrant, Apprentice Alf and Apprentice Harper</string>
|
||||
<string>Copyright © 2010–2015 Apprentice Alf and Apprentice Harper</string>
|
||||
<key>WindowState</key>
|
||||
<dict>
|
||||
<key>bundleDividerCollapsed</key>
|
||||
@@ -58,7 +58,7 @@
|
||||
<key>positionOfDivider</key>
|
||||
<real>439</real>
|
||||
<key>savedFrame</key>
|
||||
<string>77 69 1246 778 0 0 1440 877 </string>
|
||||
<string>128 98 1246 778 0 0 1680 1027 </string>
|
||||
<key>selectedTab</key>
|
||||
<string>log</string>
|
||||
</dict>
|
||||
|
||||
@@ -24,28 +24,17 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Changes at Barnes & Noble</h3>
|
||||
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that most users will now find that no combination of their name and CC# will work in decrypting their recently downloaded ebooks.</p>
|
||||
|
||||
<p>There is a work-around. Barnes & Noble’s desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from <a href="https://yuzu.com/nsdownload">https://yuzu.com/nsdownload</a>.</p>
|
||||
<p>Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.</p>
|
||||
<p>Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.</p>
|
||||
<p>If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:</p>
|
||||
<ol><li>In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.</li>
|
||||
<li>Paste the copied log into a text editor</li>
|
||||
<li>Search for the text CCHashResponseV1</li>
|
||||
<li>On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.</li>
|
||||
<li>Save that text in a new <b>plain text</b> file, with file name extension .b64 (for example, key.b64)</li>
|
||||
<li>Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.</li>
|
||||
</ol>
|
||||
<p>Someone commenting at Apprentice Alf's blog detailed a way to retrieve a new account key using the account's email address and password. This method has now been incorporated into the plugin.
|
||||
|
||||
|
||||
<h3>Old instructions: Creating New Keys:</h3>
|
||||
<h3>Creating New Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
|
||||
<ul>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. 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.</li>
|
||||
<li><span class="bold">Your Name:</span> This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, it’s your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes it’s the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. 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.</li>
|
||||
<li><span class="bold">Credit Card#:</span> this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, 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.</li>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (account email address) it was created with.</li>
|
||||
<li><span class="bold">B&N/nook account email address:</span> This is the default email address for your Barnes and Noble/nook account. This email will not be stored anywhere on your computer or in calibre. It will only be used to fetch the account key that from the B&N server, and it is that key that will be stored in the preferences.</li>
|
||||
<li><span class="bold">B&N/nook account password:</span> this is the password for your Barnes and Noble/nook account. As with the email address, this will not be stored anywhere on your computer or in calibre. It will only be used to fetch the key from the B&N server.</li>
|
||||
</ul>
|
||||
|
||||
<p>Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.</p>
|
||||
@@ -69,6 +58,11 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
<h3>NOOK Study</h3>
|
||||
<p>Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.</p>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.2.2)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -39,13 +39,16 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
||||
# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
|
||||
# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
|
||||
# 6.2.1 - Fix for non-ascii Windows user names
|
||||
# 6.2.2 - Added URL method for B&N/nook books
|
||||
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 0)
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 2)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -89,7 +92,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -252,7 +255,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue)
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
# android.py
|
||||
# Copyright © 2013-2015 by Thom and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||
# 1.1 - map_data_storage.db decryption to serial number
|
||||
# 1.2 - BugFix
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
@@ -80,7 +88,7 @@ def get_serials(path=None):
|
||||
if path is None and os.path.isfile("backup.ab"):
|
||||
return get_storage()
|
||||
|
||||
if not os.path.isfile(path):
|
||||
if path is None or not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
|
||||
@@ -521,14 +521,14 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
|
||||
u"account or on your credit card.</p>" +
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
|
||||
u"account.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere " +
|
||||
u"key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>(ex: Jonathan Smith)"))
|
||||
u"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
@@ -536,13 +536,12 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
|
||||
u"in your B&N account.</p>" +
|
||||
u"<p>No spaces or dashes... just the numbers. " +
|
||||
u"This number will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere in " +
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
|
||||
u"for your B&N account.</p>" +
|
||||
u"<p>The password will only be used to generate this " +
|
||||
u"key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
@@ -563,8 +562,8 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
return generate_bandn_key(self.user_name,self.cc_number)
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
||||
return fetch_bandn_key(self.user_name,self.cc_number)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
@@ -572,16 +571,13 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||
return unicode(self.cc_ledit.text()).strip()
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
|
||||
@@ -18,7 +18,7 @@ from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -13,13 +13,14 @@ from __future__ import with_statement
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -143,7 +144,7 @@ def getNookLogFiles():
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
|
||||
for path in paths:
|
||||
# look for nookStudy log file
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
@@ -199,7 +200,7 @@ def nookkeys(files = []):
|
||||
if fileKeys:
|
||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
||||
keys.extend(fileKeys)
|
||||
return keys
|
||||
return list(set(keys))
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
@@ -209,7 +210,7 @@ def getkey(outpath, files=[]):
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
keyfileout.write(keys[-1])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.1
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Based on discoveries by "Nobody You Know"
|
||||
# Code partly based on ignoblekeygen.py by several people.
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
||||
# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial version
|
||||
# 1.1 - Try second URL if first one fails
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeyfetch.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 fetch_key(email, password):
|
||||
# change name and CC numbers to utf-8 if unicode
|
||||
if type(email)==unicode:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2, re
|
||||
|
||||
# try the URL from nook for PC
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
if len(found)!=28:
|
||||
# try the URL from android devices
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print u"Failed to fetch key."
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"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 email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").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"Fetch", 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):
|
||||
email = self.name.get()
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -2,6 +2,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
import binascii
|
||||
@@ -164,7 +171,7 @@ def getKindlePids(rec209, token, serialnum):
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
serialnum = serialnum.encode('ascii')
|
||||
serialnum = serialnum.encode('utf-8')
|
||||
|
||||
# Compute book PID
|
||||
pidHash = SHA1(serialnum+rec209+token)
|
||||
@@ -190,16 +197,16 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex').encode('ascii')
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex').encode('ascii')
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex').encode('ascii')
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex').encode('ascii')
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
#
|
||||
# Currently requires alfcrypto.py which requires the alfcrypto library
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -20,6 +18,7 @@ from __future__ import with_statement
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
# 2.0 - Added comments and extra fix for non-ascii Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -885,6 +884,7 @@ if iswindows:
|
||||
return "AlternateUserName"
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
@@ -1161,10 +1161,10 @@ if iswindows:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
DB = {}
|
||||
return DB
|
||||
|
||||
@@ -1,635 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# DeDRM.pyw, version 6.0.1
|
||||
# Copyright 2010-2013 some_updates and Apprentice Alf
|
||||
|
||||
# Revision history:
|
||||
# 6.0.0 - Release along with unified plugin
|
||||
# 6.0.1 - Bug Fixes for Windows App
|
||||
# 6.0.2 - Fixed problem with spaces in paths and the bat file
|
||||
# 6.0.3 - Fix for Windows non-ascii user names
|
||||
# 6.0.4 - Fix for other potential unicode problems
|
||||
# 6.0.5 - Fix typo
|
||||
# 6.2.0 - Update to match plugin and AppleScript
|
||||
|
||||
__version__ = '6.2.0'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
sys.path.append(os.path.join(sys.path[0],"lib"))
|
||||
import sys, os
|
||||
import codecs
|
||||
|
||||
from argv_utils import add_cp65001_codec, set_utf8_default_encoding, unicode_argv
|
||||
add_cp65001_codec()
|
||||
set_utf8_default_encoding()
|
||||
|
||||
|
||||
import shutil
|
||||
import Tkinter
|
||||
from Tkinter import *
|
||||
import Tkconstants
|
||||
import tkFileDialog
|
||||
from scrolltextwidget import ScrolledText
|
||||
from activitybar import ActivityBar
|
||||
if sys.platform.startswith("win"):
|
||||
from askfolder_ed import AskFolder
|
||||
import re
|
||||
import simpleprefs
|
||||
import traceback
|
||||
|
||||
from Queue import Full
|
||||
from Queue import Empty
|
||||
from multiprocessing import Process, Queue
|
||||
|
||||
from scriptinterface import decryptepub, decryptpdb, decryptpdf, decryptk4mobi
|
||||
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and appended to shared queue
|
||||
class QueuedUTF8Stream:
|
||||
def __init__(self, stream, q):
|
||||
self.stream = stream
|
||||
self.encoding = 'utf-8'
|
||||
self.q = q
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode('utf-8',"replace")
|
||||
self.q.put(data)
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
class DrmException(Exception):
|
||||
pass
|
||||
|
||||
class MainApp(Tk):
|
||||
def __init__(self, apphome, dnd=False, filenames=[]):
|
||||
Tk.__init__(self)
|
||||
self.withdraw()
|
||||
self.dnd = dnd
|
||||
self.apphome = apphome
|
||||
|
||||
# preference settings
|
||||
# [dictionary key, file in preferences directory where info is stored]
|
||||
description = [ ['pids' , 'pidlist.txt' ],
|
||||
['serials', 'seriallist.txt'],
|
||||
['sdrms' , 'sdrmlist.txt' ],
|
||||
['outdir' , 'outdir.txt' ]]
|
||||
self.po = simpleprefs.SimplePrefs("DeDRM",description)
|
||||
if self.dnd:
|
||||
self.cd = ConvDialog(self)
|
||||
prefs = self.getPreferences()
|
||||
self.cd.doit(prefs, filenames)
|
||||
else:
|
||||
prefs = self.getPreferences()
|
||||
self.pd = PrefsDialog(self, prefs)
|
||||
self.cd = ConvDialog(self)
|
||||
self.pd.show()
|
||||
|
||||
def getPreferences(self):
|
||||
prefs = self.po.getPreferences()
|
||||
prefdir = prefs['dir']
|
||||
adeptkeyfile = os.path.join(prefdir,'adeptkey.der')
|
||||
if not os.path.exists(adeptkeyfile):
|
||||
import adobekey
|
||||
try:
|
||||
adobekey.getkey(adeptkeyfile)
|
||||
except:
|
||||
pass
|
||||
kindlekeyfile = os.path.join(prefdir,'kindlekey.k4i')
|
||||
if not os.path.exists(kindlekeyfile):
|
||||
import kindlekey
|
||||
try:
|
||||
kindlekey.getkey(kindlekeyfile)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
bnepubkeyfile = os.path.join(prefdir,'bnepubkey.b64')
|
||||
if not os.path.exists(bnepubkeyfile):
|
||||
import ignoblekey
|
||||
try:
|
||||
ignoblekey.getkey(bnepubkeyfile)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
return prefs
|
||||
|
||||
def setPreferences(self, newprefs):
|
||||
prefdir = self.po.prefdir
|
||||
if 'adkfile' in newprefs:
|
||||
dfile = newprefs['adkfile']
|
||||
fname = os.path.basename(dfile)
|
||||
nfile = os.path.join(prefdir,fname)
|
||||
if os.path.isfile(dfile):
|
||||
shutil.copyfile(dfile,nfile)
|
||||
if 'bnkfile' in newprefs:
|
||||
dfile = newprefs['bnkfile']
|
||||
fname = os.path.basename(dfile)
|
||||
nfile = os.path.join(prefdir,fname)
|
||||
if os.path.isfile(dfile):
|
||||
shutil.copyfile(dfile,nfile)
|
||||
if 'kinfofile' in newprefs:
|
||||
dfile = newprefs['kinfofile']
|
||||
fname = os.path.basename(dfile)
|
||||
nfile = os.path.join(prefdir,fname)
|
||||
if os.path.isfile(dfile):
|
||||
shutil.copyfile(dfile,nfile)
|
||||
self.po.setPreferences(newprefs)
|
||||
return
|
||||
|
||||
def alldone(self):
|
||||
if not self.dnd:
|
||||
self.pd.enablebuttons()
|
||||
else:
|
||||
self.destroy()
|
||||
|
||||
class PrefsDialog(Toplevel):
|
||||
def __init__(self, mainapp, prefs_array):
|
||||
Toplevel.__init__(self, mainapp)
|
||||
self.withdraw()
|
||||
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
||||
self.title("DeDRM " + __version__)
|
||||
self.prefs_array = prefs_array
|
||||
self.status = Tkinter.Label(self, text='Setting Preferences')
|
||||
self.status.pack(fill=Tkconstants.X, expand=1)
|
||||
body = Tkinter.Frame(self)
|
||||
self.body = body
|
||||
body.pack(fill=Tkconstants.X, expand=1)
|
||||
sticky = Tkconstants.E + Tkconstants.W
|
||||
body.grid_columnconfigure(1, weight=2)
|
||||
|
||||
Tkinter.Label(body, text='Adobe Key file (adeptkey.der)').grid(row=0, sticky=Tkconstants.E)
|
||||
self.adkpath = Tkinter.Entry(body, width=50)
|
||||
self.adkpath.grid(row=0, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'adeptkey.der')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.adkpath.insert(0, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_adkpath)
|
||||
button.grid(row=0, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Kindle Key file (kindlekey.k4i)').grid(row=1, sticky=Tkconstants.E)
|
||||
self.kkpath = Tkinter.Entry(body, width=50)
|
||||
self.kkpath.grid(row=1, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'kindlekey.k4i')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.kkpath.insert(1, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_kkpath)
|
||||
button.grid(row=1, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Barnes and Noble Key file (bnepubkey.b64)').grid(row=2, sticky=Tkconstants.E)
|
||||
self.bnkpath = Tkinter.Entry(body, width=50)
|
||||
self.bnkpath.grid(row=2, column=1, sticky=sticky)
|
||||
prefdir = self.prefs_array['dir']
|
||||
keyfile = os.path.join(prefdir,'bnepubkey.b64')
|
||||
if os.path.isfile(keyfile):
|
||||
path = keyfile
|
||||
self.bnkpath.insert(2, path)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_bnkpath)
|
||||
button.grid(row=2, column=2)
|
||||
|
||||
Tkinter.Label(body, text='Mobipocket PID list\n(8 or 10 characters, comma separated)').grid(row=3, sticky=Tkconstants.E)
|
||||
self.pidnums = Tkinter.StringVar()
|
||||
self.pidinfo = Tkinter.Entry(body, width=50, textvariable=self.pidnums)
|
||||
if 'pids' in self.prefs_array:
|
||||
self.pidnums.set(self.prefs_array['pids'])
|
||||
self.pidinfo.grid(row=3, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text='eInk Kindle Serial Number list\n(16 characters, comma separated)').grid(row=4, sticky=Tkconstants.E)
|
||||
self.sernums = Tkinter.StringVar()
|
||||
self.serinfo = Tkinter.Entry(body, width=50, textvariable=self.sernums)
|
||||
if 'serials' in self.prefs_array:
|
||||
self.sernums.set(self.prefs_array['serials'])
|
||||
self.serinfo.grid(row=4, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text='eReader data list\n(name:last 8 digits on credit card, comma separated)').grid(row=5, sticky=Tkconstants.E)
|
||||
self.sdrmnums = Tkinter.StringVar()
|
||||
self.sdrminfo = Tkinter.Entry(body, width=50, textvariable=self.sdrmnums)
|
||||
if 'sdrms' in self.prefs_array:
|
||||
self.sdrmnums.set(self.prefs_array['sdrms'])
|
||||
self.sdrminfo.grid(row=5, column=1, sticky=sticky)
|
||||
|
||||
Tkinter.Label(body, text="Output Folder (if blank, use input ebook's folder)").grid(row=6, sticky=Tkconstants.E)
|
||||
self.outpath = Tkinter.Entry(body, width=50)
|
||||
self.outpath.grid(row=6, column=1, sticky=sticky)
|
||||
if 'outdir' in self.prefs_array:
|
||||
dpath = self.prefs_array['outdir']
|
||||
self.outpath.insert(0, dpath)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_outpath)
|
||||
button.grid(row=6, column=2)
|
||||
|
||||
Tkinter.Label(body, text='').grid(row=7, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
|
||||
Tkinter.Label(body, text='Alternatively Process an eBook').grid(row=8, column=0, columnspan=2, sticky=Tkconstants.N)
|
||||
|
||||
Tkinter.Label(body, text='Select an eBook to Process*').grid(row=9, sticky=Tkconstants.E)
|
||||
self.bookpath = Tkinter.Entry(body, width=50)
|
||||
self.bookpath.grid(row=9, column=1, sticky=sticky)
|
||||
button = Tkinter.Button(body, text="...", command=self.get_bookpath)
|
||||
button.grid(row=9, column=2)
|
||||
|
||||
Tkinter.Label(body, font=("Helvetica", "10", "italic"), text='*To DeDRM multiple ebooks simultaneously, set your preferences and quit.\nThen drag and drop ebooks or folders onto the DeDRM_Drop_Target').grid(row=10, column=1, sticky=Tkconstants.E)
|
||||
|
||||
Tkinter.Label(body, text='').grid(row=11, column=0, columnspan=2, sticky=Tkconstants.E)
|
||||
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
self.sbotton = Tkinter.Button(buttons, text="Set Prefs", width=14, command=self.setprefs)
|
||||
self.sbotton.pack(side=Tkconstants.LEFT)
|
||||
|
||||
buttons.pack()
|
||||
self.pbotton = Tkinter.Button(buttons, text="Process eBook", width=14, command=self.doit)
|
||||
self.pbotton.pack(side=Tkconstants.LEFT)
|
||||
buttons.pack()
|
||||
self.qbotton = Tkinter.Button(buttons, text="Quit", width=14, command=self.quitting)
|
||||
self.qbotton.pack(side=Tkconstants.RIGHT)
|
||||
buttons.pack()
|
||||
|
||||
def disablebuttons(self):
|
||||
self.sbotton.configure(state='disabled')
|
||||
self.pbotton.configure(state='disabled')
|
||||
self.qbotton.configure(state='disabled')
|
||||
|
||||
def enablebuttons(self):
|
||||
self.sbotton.configure(state='normal')
|
||||
self.pbotton.configure(state='normal')
|
||||
self.qbotton.configure(state='normal')
|
||||
|
||||
def show(self):
|
||||
self.deiconify()
|
||||
self.tkraise()
|
||||
|
||||
def hide(self):
|
||||
self.withdraw()
|
||||
|
||||
def get_outpath(self):
|
||||
cpath = self.outpath.get()
|
||||
if sys.platform.startswith("win"):
|
||||
# tk_chooseDirectory is horribly broken for unicode paths
|
||||
# on windows - bug has been reported but not fixed for years
|
||||
# workaround by using our own unicode aware version
|
||||
outpath = AskFolder(message="Choose the folder for DRM-free ebooks",
|
||||
defaultLocation=cpath)
|
||||
else:
|
||||
outpath = tkFileDialog.askdirectory(
|
||||
parent=None, title='Choose the folder for DRM-free ebooks',
|
||||
initialdir=cpath, initialfile=None)
|
||||
if outpath:
|
||||
outpath = os.path.normpath(outpath)
|
||||
self.outpath.delete(0, Tkconstants.END)
|
||||
self.outpath.insert(0, outpath)
|
||||
return
|
||||
|
||||
def get_adkpath(self):
|
||||
cpath = self.adkpath.get()
|
||||
adkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Adept Key file',
|
||||
defaultextension='.der', filetypes=[('Adept Key file', '.der'), ('All Files', '.*')])
|
||||
if adkpath:
|
||||
adkpath = os.path.normpath(adkpath)
|
||||
self.adkpath.delete(0, Tkconstants.END)
|
||||
self.adkpath.insert(0, adkpath)
|
||||
return
|
||||
|
||||
def get_kkpath(self):
|
||||
cpath = self.kkpath.get()
|
||||
kkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Kindle Key file',
|
||||
defaultextension='.k4i', filetypes=[('Kindle Key file', '.k4i'), ('All Files', '.*')])
|
||||
if kkpath:
|
||||
kkpath = os.path.normpath(kkpath)
|
||||
self.kkpath.delete(0, Tkconstants.END)
|
||||
self.kkpath.insert(0, kkpath)
|
||||
return
|
||||
|
||||
def get_bnkpath(self):
|
||||
cpath = self.bnkpath.get()
|
||||
bnkpath = tkFileDialog.askopenfilename(initialdir = cpath, parent=None, title='Select Barnes and Noble Key file',
|
||||
defaultextension='.b64', filetypes=[('Barnes and Noble Key file', '.b64'), ('All Files', '.*')])
|
||||
if bnkpath:
|
||||
bnkpath = os.path.normpath(bnkpath)
|
||||
self.bnkpath.delete(0, Tkconstants.END)
|
||||
self.bnkpath.insert(0, bnkpath)
|
||||
return
|
||||
|
||||
def get_bookpath(self):
|
||||
cpath = self.bookpath.get()
|
||||
bookpath = tkFileDialog.askopenfilename(parent=None, title='Select eBook for DRM Removal',
|
||||
filetypes=[('All Files', '.*'),
|
||||
('ePub Files','.epub'),
|
||||
('Kindle','.azw'),
|
||||
('Kindle','.azw1'),
|
||||
('Kindle','.azw3'),
|
||||
('Kindle','.azw4'),
|
||||
('Kindle','.tpz'),
|
||||
('Kindle','.mobi'),
|
||||
('Kindle','.prc'),
|
||||
('eReader','.pdb'),
|
||||
('PDF','.pdf')],
|
||||
initialdir=cpath)
|
||||
if bookpath:
|
||||
bookpath = os.path.normpath(bookpath)
|
||||
self.bookpath.delete(0, Tkconstants.END)
|
||||
self.bookpath.insert(0, bookpath)
|
||||
return
|
||||
|
||||
def quitting(self):
|
||||
self.master.destroy()
|
||||
|
||||
def setprefs(self):
|
||||
# setting new prefereces
|
||||
new_prefs = {}
|
||||
prefdir = self.prefs_array['dir']
|
||||
new_prefs['dir'] = prefdir
|
||||
new_prefs['pids'] = self.pidinfo.get().replace(" ","")
|
||||
new_prefs['serials'] = self.serinfo.get().replace(" ","")
|
||||
new_prefs['sdrms'] = self.sdrminfo.get().strip().replace(", ",",")
|
||||
new_prefs['outdir'] = self.outpath.get().strip()
|
||||
adkpath = self.adkpath.get()
|
||||
if os.path.dirname(adkpath) != prefdir:
|
||||
new_prefs['adkfile'] = adkpath
|
||||
bnkpath = self.bnkpath.get()
|
||||
if os.path.dirname(bnkpath) != prefdir:
|
||||
new_prefs['bnkfile'] = bnkpath
|
||||
kkpath = self.kkpath.get()
|
||||
if os.path.dirname(kkpath) != prefdir:
|
||||
new_prefs['kindlefile'] = kkpath
|
||||
self.master.setPreferences(new_prefs)
|
||||
# and update internal copies
|
||||
self.prefs_array['pids'] = new_prefs['pids']
|
||||
self.prefs_array['serials'] = new_prefs['serials']
|
||||
self.prefs_array['sdrms'] = new_prefs['sdrms']
|
||||
self.prefs_array['outdir'] = new_prefs['outdir']
|
||||
|
||||
def doit(self):
|
||||
self.disablebuttons()
|
||||
filenames=[]
|
||||
bookpath = self.bookpath.get()
|
||||
bookpath = os.path.abspath(bookpath)
|
||||
filenames.append(bookpath)
|
||||
self.master.cd.doit(self.prefs_array,filenames)
|
||||
|
||||
|
||||
|
||||
class ConvDialog(Toplevel):
|
||||
def __init__(self, master, prefs_array={}, filenames=[]):
|
||||
Toplevel.__init__(self, master)
|
||||
self.withdraw()
|
||||
self.protocol("WM_DELETE_WINDOW", self.withdraw)
|
||||
self.title("DeDRM Processing")
|
||||
self.master = master
|
||||
self.apphome = self.master.apphome
|
||||
self.prefs_array = prefs_array
|
||||
self.filenames = filenames
|
||||
self.interval = 50
|
||||
self.p2 = None
|
||||
self.q = Queue()
|
||||
self.running = 'inactive'
|
||||
self.numgood = 0
|
||||
self.numbad = 0
|
||||
self.status = Tkinter.Label(self, text='DeDRM processing...')
|
||||
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='Activity Bar').grid(row=0, sticky=Tkconstants.E)
|
||||
self.bar = ActivityBar(body, length=80, height=15, barwidth=5)
|
||||
self.bar.grid(row=0, column=1, sticky=sticky)
|
||||
|
||||
msg1 = ''
|
||||
self.stext = ScrolledText(body, bd=5, relief=Tkconstants.RIDGE, height=4, width=80, wrap=Tkconstants.WORD)
|
||||
self.stext.grid(row=2, column=0, columnspan=2,sticky=sticky)
|
||||
self.stext.insert(Tkconstants.END,msg1)
|
||||
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
self.qbutton = Tkinter.Button(buttons, text="Quit", width=14, command=self.quitting)
|
||||
self.qbutton.pack(side=Tkconstants.BOTTOM)
|
||||
self.status['text'] = ''
|
||||
|
||||
self.logfile = open(os.path.join(os.path.expanduser('~'),'DeDRM.log'),'w')
|
||||
|
||||
def show(self):
|
||||
self.deiconify()
|
||||
self.tkraise()
|
||||
|
||||
def hide(self):
|
||||
self.withdraw()
|
||||
|
||||
def doit(self, prefs, filenames):
|
||||
self.running = 'inactive'
|
||||
self.prefs_array = prefs
|
||||
self.filenames = filenames
|
||||
self.show()
|
||||
self.processBooks()
|
||||
|
||||
def conversion_done(self):
|
||||
self.hide()
|
||||
self.master.alldone()
|
||||
|
||||
def processBooks(self):
|
||||
while self.running == 'inactive':
|
||||
rscpath = self.prefs_array['dir']
|
||||
filename = None
|
||||
if len(self.filenames) > 0:
|
||||
filename = self.filenames.pop(0)
|
||||
if filename == None:
|
||||
msg = 'Complete: '
|
||||
msg += 'Successes: %d, ' % self.numgood
|
||||
msg += 'Failures: %d\n' % self.numbad
|
||||
self.showCmdOutput(msg)
|
||||
if self.numbad == 0:
|
||||
self.after(2000,self.conversion_done())
|
||||
self.logfile.write("DeDRM v{0}: {1}".format(__version__,msg))
|
||||
self.logfile.close()
|
||||
return
|
||||
infile = filename
|
||||
bname = os.path.basename(infile)
|
||||
msg = 'Processing: ' + bname + '...'
|
||||
self.logfile.write("DeDRM v{0}: {1}\n".format(__version__,msg))
|
||||
self.showCmdOutput(msg)
|
||||
outdir = os.path.dirname(filename)
|
||||
if 'outdir' in self.prefs_array:
|
||||
dpath = self.prefs_array['outdir']
|
||||
if dpath.strip() != '':
|
||||
outdir = dpath
|
||||
rv = self.decrypt_ebook(infile, outdir, rscpath)
|
||||
if rv == 0:
|
||||
self.bar.start()
|
||||
self.running = 'active'
|
||||
self.processQueue()
|
||||
else:
|
||||
msg = 'Unknown File: ' + bname + '\n'
|
||||
self.logfile.write("DeDRM v{0}: {1}".format(__version__,msg))
|
||||
self.showCmdOutput(msg)
|
||||
self.numbad += 1
|
||||
|
||||
def quitting(self):
|
||||
# kill any still running subprocess
|
||||
self.running = 'stopped'
|
||||
if self.p2 != None:
|
||||
if (self.p2.exitcode == None):
|
||||
self.p2.terminate()
|
||||
self.conversion_done()
|
||||
|
||||
# post output from subprocess in scrolled text widget
|
||||
def showCmdOutput(self, msg):
|
||||
if msg and msg !='':
|
||||
if sys.platform.startswith('win'):
|
||||
msg = msg.replace('\r\n','\n')
|
||||
self.stext.insert(Tkconstants.END,msg)
|
||||
self.stext.yview_pickplace(Tkconstants.END)
|
||||
return
|
||||
|
||||
# read from subprocess pipe without blocking
|
||||
# invoked every interval via the widget "after"
|
||||
# option being used, so need to reset it for the next time
|
||||
def processQueue(self):
|
||||
if self.p2 == None:
|
||||
# nothing to wait for so just return
|
||||
return
|
||||
poll = self.p2.exitcode
|
||||
#print "processing", poll
|
||||
done = False
|
||||
text = ''
|
||||
while not done:
|
||||
try:
|
||||
data = self.q.get_nowait()
|
||||
text += data
|
||||
except Empty:
|
||||
done = True
|
||||
if text != '':
|
||||
self.logfile.write(text)
|
||||
if poll != None:
|
||||
self.bar.stop()
|
||||
if poll == 0:
|
||||
msg = 'Success\n'
|
||||
self.numgood += 1
|
||||
else:
|
||||
msg = 'Failed\n'
|
||||
self.numbad += 1
|
||||
self.p2.join()
|
||||
self.logfile.write("DeDRM v{0}: {1}\n".format(__version__,msg))
|
||||
self.showCmdOutput(msg)
|
||||
self.p2 = None
|
||||
self.running = 'inactive'
|
||||
self.after(50,self.processBooks)
|
||||
return
|
||||
# make sure we get invoked again by event loop after interval
|
||||
self.stext.after(self.interval,self.processQueue)
|
||||
return
|
||||
|
||||
def decrypt_ebook(self, infile, outdir, rscpath):
|
||||
q = self.q
|
||||
rv = 1
|
||||
name, ext = os.path.splitext(os.path.basename(infile))
|
||||
ext = ext.lower()
|
||||
if ext == '.epub':
|
||||
self.p2 = Process(target=processEPUB, args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
if ext == '.pdb':
|
||||
self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']:
|
||||
self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
if ext == '.pdf':
|
||||
self.p2 = Process(target=processPDF, args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
return rv
|
||||
|
||||
|
||||
# child process starts here
|
||||
def processK4MOBI(q, infile, outdir, rscpath):
|
||||
add_cp65001_codec()
|
||||
set_utf8_default_encoding()
|
||||
sys.stdout = QueuedUTF8Stream(sys.stdout, q)
|
||||
sys.stderr = QueuedUTF8Stream(sys.stderr, q)
|
||||
rv = decryptk4mobi(infile, outdir, rscpath)
|
||||
sys.exit(rv)
|
||||
|
||||
# child process starts here
|
||||
def processPDF(q, infile, outdir, rscpath):
|
||||
add_cp65001_codec()
|
||||
set_utf8_default_encoding()
|
||||
sys.stdout = QueuedUTF8Stream(sys.stdout, q)
|
||||
sys.stderr = QueuedUTF8Stream(sys.stderr, q)
|
||||
rv = decryptpdf(infile, outdir, rscpath)
|
||||
sys.exit(rv)
|
||||
|
||||
# child process starts here
|
||||
def processEPUB(q, infile, outdir, rscpath):
|
||||
add_cp65001_codec()
|
||||
set_utf8_default_encoding()
|
||||
sys.stdout = QueuedUTF8Stream(sys.stdout, q)
|
||||
sys.stderr = QueuedUTF8Stream(sys.stderr, q)
|
||||
rv = decryptepub(infile, outdir, rscpath)
|
||||
sys.exit(rv)
|
||||
|
||||
# child process starts here
|
||||
def processPDB(q, infile, outdir, rscpath):
|
||||
add_cp65001_codec()
|
||||
set_utf8_default_encoding()
|
||||
sys.stdout = QueuedUTF8Stream(sys.stdout, q)
|
||||
sys.stderr = QueuedUTF8Stream(sys.stderr, q)
|
||||
rv = decryptpdb(infile, outdir, rscpath)
|
||||
sys.exit(rv)
|
||||
|
||||
|
||||
def main():
|
||||
argv=unicode_argv()
|
||||
apphome = os.path.dirname(argv[0])
|
||||
apphome = os.path.abspath(apphome)
|
||||
|
||||
# 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
|
||||
if sys.platform.startswith('win') and len(argv) == 2:
|
||||
temp = argv[1]
|
||||
temp = temp.strip('"')
|
||||
temp = temp.strip()
|
||||
if temp == '':
|
||||
argv.pop()
|
||||
|
||||
if len(argv) == 1:
|
||||
filenames = []
|
||||
dnd = False
|
||||
|
||||
else : # processing books via drag and drop
|
||||
dnd = True
|
||||
# build a list of the files to be processed
|
||||
# note all filenames and paths have been utf-8 encoded
|
||||
infilelst = argv[1:]
|
||||
filenames = []
|
||||
for infile in infilelst:
|
||||
infile = infile.replace('"','')
|
||||
infile = os.path.abspath(infile)
|
||||
if os.path.isdir(infile):
|
||||
bpath = infile
|
||||
filelst = os.listdir(infile)
|
||||
for afile in filelst:
|
||||
if not afile.startswith('.'):
|
||||
filepath = os.path.join(bpath,afile)
|
||||
if os.path.isfile(filepath):
|
||||
filenames.append(filepath)
|
||||
else :
|
||||
afile = os.path.basename(infile)
|
||||
if not afile.startswith('.'):
|
||||
if os.path.isfile(infile):
|
||||
filenames.append(infile)
|
||||
|
||||
# start up gui app
|
||||
app = MainApp(apphome, dnd, filenames)
|
||||
app.mainloop()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -12,8 +12,11 @@
|
||||
# 6.0.4 - Fix for other potential unicode problems
|
||||
# 6.0.5 - Fix typo
|
||||
# 6.2.0 - Update to match plugin and AppleScript
|
||||
# 6.2.1 - Fix for non-ascii user names
|
||||
# 6.2.2 - Added URL method for B&N/nook books
|
||||
|
||||
__version__ = '6.2.0'
|
||||
|
||||
__version__ = '6.2.2'
|
||||
|
||||
import sys
|
||||
import os, os.path
|
||||
@@ -536,7 +539,7 @@ class ConvDialog(Toplevel):
|
||||
self.p2 = Process(target=processPDB, args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.tpz']:
|
||||
if ext in ['.azw', '.azw1', '.azw3', '.azw4', '.prc', '.mobi', '.pobi', '.tpz']:
|
||||
self.p2 = Process(target=processK4MOBI,args=(q, infile, outdir, rscpath))
|
||||
self.p2.start()
|
||||
return 0
|
||||
@@ -588,7 +591,7 @@ def main():
|
||||
argv=unicode_argv()
|
||||
apphome = os.path.dirname(argv[0])
|
||||
apphome = os.path.abspath(apphome)
|
||||
|
||||
|
||||
# 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
|
||||
if sys.platform.startswith('win') and len(argv) == 2:
|
||||
|
||||
@@ -24,28 +24,17 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Changes at Barnes & Noble</h3>
|
||||
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that most users will now find that no combination of their name and CC# will work in decrypting their recently downloaded ebooks.</p>
|
||||
|
||||
<p>There is a work-around. Barnes & Noble’s desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from <a href="https://yuzu.com/nsdownload">https://yuzu.com/nsdownload</a>.</p>
|
||||
<p>Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.</p>
|
||||
<p>Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.</p>
|
||||
<p>If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:</p>
|
||||
<ol><li>In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.</li>
|
||||
<li>Paste the copied log into a text editor</li>
|
||||
<li>Search for the text CCHashResponseV1</li>
|
||||
<li>On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.</li>
|
||||
<li>Save that text in a new <b>plain text</b> file, with file name extension .b64 (for example, key.b64)</li>
|
||||
<li>Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.</li>
|
||||
</ol>
|
||||
<p>Someone commenting at Apprentice Alf's blog detailed a way to retrieve a new account key using the account's email address and password. This method has now been incorporated into the plugin.
|
||||
|
||||
|
||||
<h3>Old instructions: Creating New Keys:</h3>
|
||||
<h3>Creating New Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
|
||||
<ul>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. 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.</li>
|
||||
<li><span class="bold">Your Name:</span> This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, it’s your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes it’s the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. 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.</li>
|
||||
<li><span class="bold">Credit Card#:</span> this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, 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.</li>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (account email address) it was created with.</li>
|
||||
<li><span class="bold">B&N/nook account email address:</span> This is the default email address for your Barnes and Noble/nook account. This email will not be stored anywhere on your computer or in calibre. It will only be used to fetch the account key that from the B&N server, and it is that key that will be stored in the preferences.</li>
|
||||
<li><span class="bold">B&N/nook account password:</span> this is the password for your Barnes and Noble/nook account. As with the email address, this will not be stored anywhere on your computer or in calibre. It will only be used to fetch the key from the B&N server.</li>
|
||||
</ul>
|
||||
|
||||
<p>Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.</p>
|
||||
@@ -69,6 +58,11 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
<h3>NOOK Study</h3>
|
||||
<p>Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.</p>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.2.2)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
@@ -39,13 +39,16 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
||||
# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
|
||||
# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
|
||||
# 6.2.1 - Fix for non-ascii Windows user names
|
||||
# 6.2.2 - Added URL method for B&N/nook books
|
||||
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 0)
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 2)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -89,7 +92,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -252,7 +255,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue)
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
# android.py
|
||||
# Copyright © 2013-2015 by Thom and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||
# 1.1 - map_data_storage.db decryption to serial number
|
||||
# 1.2 - BugFix
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
@@ -80,7 +88,7 @@ def get_serials(path=None):
|
||||
if path is None and os.path.isfile("backup.ab"):
|
||||
return get_storage()
|
||||
|
||||
if not os.path.isfile(path):
|
||||
if path is None or not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
|
||||
@@ -521,14 +521,14 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
|
||||
u"account or on your credit card.</p>" +
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
|
||||
u"account.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere " +
|
||||
u"key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>(ex: Jonathan Smith)"))
|
||||
u"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
@@ -536,13 +536,12 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
|
||||
u"in your B&N account.</p>" +
|
||||
u"<p>No spaces or dashes... just the numbers. " +
|
||||
u"This number will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere in " +
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
|
||||
u"for your B&N account.</p>" +
|
||||
u"<p>The password will only be used to generate this " +
|
||||
u"key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
@@ -563,8 +562,8 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
return generate_bandn_key(self.user_name,self.cc_number)
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
||||
return fetch_bandn_key(self.user_name,self.cc_number)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
@@ -572,16 +571,13 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||
return unicode(self.cc_ledit.text()).strip()
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
|
||||
@@ -18,7 +18,7 @@ from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -13,13 +13,14 @@ from __future__ import with_statement
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -143,7 +144,7 @@ def getNookLogFiles():
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
|
||||
for path in paths:
|
||||
# look for nookStudy log file
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
@@ -199,7 +200,7 @@ def nookkeys(files = []):
|
||||
if fileKeys:
|
||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
||||
keys.extend(fileKeys)
|
||||
return keys
|
||||
return list(set(keys))
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
@@ -209,7 +210,7 @@ def getkey(outpath, files=[]):
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
keyfileout.write(keys[-1])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.1
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Based on discoveries by "Nobody You Know"
|
||||
# Code partly based on ignoblekeygen.py by several people.
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
||||
# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial version
|
||||
# 1.1 - Try second URL if first one fails
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeyfetch.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 fetch_key(email, password):
|
||||
# change name and CC numbers to utf-8 if unicode
|
||||
if type(email)==unicode:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2, re
|
||||
|
||||
# try the URL from nook for PC
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
if len(found)!=28:
|
||||
# try the URL from android devices
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print u"Failed to fetch key."
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"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 email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").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"Fetch", 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):
|
||||
email = self.name.get()
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -2,6 +2,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
import binascii
|
||||
@@ -164,7 +171,7 @@ def getKindlePids(rec209, token, serialnum):
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
serialnum = serialnum.encode('ascii')
|
||||
serialnum = serialnum.encode('utf-8')
|
||||
|
||||
# Compute book PID
|
||||
pidHash = SHA1(serialnum+rec209+token)
|
||||
@@ -190,16 +197,16 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex').encode('ascii')
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex').encode('ascii')
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex').encode('ascii')
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex').encode('ascii')
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
#
|
||||
# Currently requires alfcrypto.py which requires the alfcrypto library
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -20,6 +18,7 @@ from __future__ import with_statement
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
# 2.0 - Added comments and extra fix for non-ascii Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -885,6 +884,7 @@ if iswindows:
|
||||
return "AlternateUserName"
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
@@ -1161,10 +1161,10 @@ if iswindows:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
DB = {}
|
||||
return DB
|
||||
|
||||
@@ -20,7 +20,7 @@ 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.
|
||||
|
||||
1. Drag the DeDRM_App folder from tools_v6.2.0/DeDRM_Application_Windows to your "My Documents" folder.
|
||||
1. Drag the DeDRM_App folder from tools_v6.2.2/DeDRM_Application_Windows to your "My Documents" folder.
|
||||
|
||||
2. Open the DeDRM_App 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.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -24,28 +24,17 @@ li {margin-top: 0.5em}
|
||||
|
||||
<h3>Changes at Barnes & Noble</h3>
|
||||
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that some users will find that no combination of their name and CC# will work in decrypting their ebooks.</p>
|
||||
<p>In mid-2014, Barnes & Noble changed the way they generated encryption keys. Instead of deriving the key from the user's name and credit card number, they started generating a random key themselves, sending that key through to devices when they connected to the Barnes & Noble servers. This means that most users will now find that no combination of their name and CC# will work in decrypting their recently downloaded ebooks.</p>
|
||||
|
||||
<p>There is a work-around. Barnes & Noble’s desktop app NOOK Study generates a log file that contains the encryption key. You can download NOOK Study from <a href="https://yuzu.com/nsdownload">https://yuzu.com/nsdownload</a>.</p>
|
||||
<p>Once downloaded, install the application, register with your Barnes & Noble or nook account, and download at least one DRMed ebook through NOOK Study. It will be saved somewhere in a folder called "My Barnes & Noble eBooks" in your Documents folder.</p>
|
||||
<p>Now import that book into calibre. The log file and the key in the log should be automatically found by the plugin and used to decrypt the book.</p>
|
||||
<p>If the automatic process doesn't work for you, you can still find extract it manually and save it as a .b64 file for import into the plugin's preferences as follows:</p>
|
||||
<ol><li>In NOOK Study, select Settings/About (Windows) or NOOK Study/About NOOK Study (Mac) and in the dialog that appears click the link at the bottom to copy the log into the clipboard.</li>
|
||||
<li>Paste the copied log into a text editor</li>
|
||||
<li>Search for the text CCHashResponseV1</li>
|
||||
<li>On the line below which starts with ccHash, copy the text between the " marks after ccHash, but don't include the " marks.</li>
|
||||
<li>Save that text in a new <b>plain text</b> file, with file name extension .b64 (for example, key.b64)</li>
|
||||
<li>Import that file into the preferences through this dialog, using the "Import Existing Key Files" button.</li>
|
||||
</ol>
|
||||
<p>Someone commenting at Apprentice Alf's blog detailed a way to retrieve a new account key using the account's email address and password. This method has now been incorporated into the plugin.
|
||||
|
||||
|
||||
<h3>Old instructions: Creating New Keys:</h3>
|
||||
<h3>Creating New Keys:</h3>
|
||||
|
||||
<p>On the right-hand side of the plugin’s customization dialog, you will see a button with an icon that looks like a green plus sign (+). Clicking this button will open a new dialog for entering the necessary data to generate a new key.</p>
|
||||
<ul>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. 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.</li>
|
||||
<li><span class="bold">Your Name:</span> This is the name used by Barnes and Noble to generate your encryption key. Seemingly at random, Barnes and Noble choose one of three places from which to take this name. Most commonly, it’s your name as set in your Barnes & Noble account, My Account page, directly under PERSONAL INFORMATION. Sometimes it is the the name used in the default shipping address, and sometimes it’s the name listed for the active credit card. If these names are different in your Barnes and Noble account preferences, I suggest creating one key for each version of your name. 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.</li>
|
||||
<li><span class="bold">Credit Card#:</span> this is the default credit card number that was on file with Barnes and Noble at the time of download of the ebook to be de-DRMed. Just enter the 16 (15 for American Express) digits. As with the name, 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.</li>
|
||||
<li><span class="bold">Unique Key Name:</span> this is a unique name you choose to help you identify the key. This name will show in the list of configured keys. Choose something that will help you remember the data (account email address) it was created with.</li>
|
||||
<li><span class="bold">B&N/nook account email address:</span> This is the default email address for your Barnes and Noble/nook account. This email will not be stored anywhere on your computer or in calibre. It will only be used to fetch the account key that from the B&N server, and it is that key that will be stored in the preferences.</li>
|
||||
<li><span class="bold">B&N/nook account password:</span> this is the password for your Barnes and Noble/nook account. As with the email address, this will not be stored anywhere on your computer or in calibre. It will only be used to fetch the key from the B&N server.</li>
|
||||
</ul>
|
||||
|
||||
<p>Click the OK button to create and store the generated key. Or Cancel if you don’t want to create a key.</p>
|
||||
@@ -69,6 +58,11 @@ li {margin-top: 0.5em}
|
||||
|
||||
<p>Once done creating/deleting/renaming/importing decryption keys, click Close to exit the customization dialogue. Your changes wil only be saved permanently when you click OK in the main configuration dialog.</p>
|
||||
|
||||
<h3>NOOK Study</h3>
|
||||
<p>Books downloaded through NOOK Study may or may not use the key found using the above method. If a book is not decrypted successfully with any of the keys, the plugin will attempt to recover keys from the NOOK Study log file and use them.</p>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v6.1.0)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v6.2.2)</span></h1>
|
||||
|
||||
<p>This plugin removes DRM from ebooks when they are imported into calibre. If you already have DRMed ebooks in your calibre library, you will need to remove them and import them again.</p>
|
||||
|
||||
|
||||
@@ -39,13 +39,16 @@ __docformat__ = 'restructuredtext en'
|
||||
# 6.1.0 - Fixed multiple books import problem and PDF import with no key problem
|
||||
# 6.2.0 - Support for getting B&N key from nook Study log. Fix for UTF-8 filenames in Adobe ePubs.
|
||||
# Fix for not copying needed files. Fix for getting default Adobe key for PDFs
|
||||
# 6.2.1 - Fix for non-ascii Windows user names
|
||||
# 6.2.2 - Added URL method for B&N/nook books
|
||||
|
||||
|
||||
"""
|
||||
Decrypt DRMed ebooks.
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = u"DeDRM"
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 0)
|
||||
PLUGIN_VERSION_TUPLE = (6, 2, 2)
|
||||
PLUGIN_VERSION = u".".join([unicode(str(x)) for x in PLUGIN_VERSION_TUPLE])
|
||||
# Include an html helpfile in the plugin's zipfile with the following name.
|
||||
RESOURCE_NAME = PLUGIN_NAME + '_Help.htm'
|
||||
@@ -89,7 +92,7 @@ class DeDRM(FileTypePlugin):
|
||||
author = u"DiapDealer, Apprentice Alf, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
minimum_calibre_version = (0, 7, 55) # Compiled python libraries cannot be imported in earlier versions.
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','azw','azw1','azw3','azw4','tpz'])
|
||||
file_types = set(['epub','pdf','pdb','prc','mobi','pobi','azw','azw1','azw3','azw4','tpz'])
|
||||
on_import = True
|
||||
priority = 600
|
||||
|
||||
@@ -252,7 +255,7 @@ class DeDRM(FileTypePlugin):
|
||||
# Store the new successful key in the defaults
|
||||
print u"{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION)
|
||||
try:
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','default_key',keyvalue)
|
||||
dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_Study_key',keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
print u"{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)
|
||||
except:
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
#fileencoding: utf-8
|
||||
|
||||
# android.py
|
||||
# Copyright © 2013-2015 by Thom and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||
# 1.1 - map_data_storage.db decryption to serial number
|
||||
# 1.2 - BugFix
|
||||
|
||||
import os
|
||||
import sys
|
||||
import zlib
|
||||
@@ -80,7 +88,7 @@ def get_serials(path=None):
|
||||
if path is None and os.path.isfile("backup.ab"):
|
||||
return get_storage()
|
||||
|
||||
if not os.path.isfile(path):
|
||||
if path is None or not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
storage = parse_preference(path)
|
||||
|
||||
@@ -521,14 +521,14 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
name_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(name_group)
|
||||
name_group.addWidget(QLabel(u"Your Name:", self))
|
||||
name_group.addWidget(QLabel(u"B&N/nook account email address:", self))
|
||||
self.name_ledit = QLineEdit(u"", self)
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your name as it appears in your B&N " +
|
||||
u"account or on your credit card.</p>" +
|
||||
self.name_ledit.setToolTip(_(u"<p>Enter your email address as it appears in your B&N " +
|
||||
u"account.</p>" +
|
||||
u"<p>It will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere " +
|
||||
u"key and won\'t be stored anywhere " +
|
||||
u"in calibre or on your computer.</p>" +
|
||||
u"<p>(ex: Jonathan Smith)"))
|
||||
u"<p>eg: apprenticeharper@gmail.com</p>"))
|
||||
name_group.addWidget(self.name_ledit)
|
||||
name_disclaimer_label = QLabel(_(u"(Will not be saved in configuration data)"), self)
|
||||
name_disclaimer_label.setAlignment(Qt.AlignHCenter)
|
||||
@@ -536,13 +536,12 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
ccn_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(ccn_group)
|
||||
ccn_group.addWidget(QLabel(u"Credit Card#:", self))
|
||||
ccn_group.addWidget(QLabel(u"B&N/nook account password:", self))
|
||||
self.cc_ledit = QLineEdit(u"", self)
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the full credit card number on record " +
|
||||
u"in your B&N account.</p>" +
|
||||
u"<p>No spaces or dashes... just the numbers. " +
|
||||
u"This number will only be used to generate this " +
|
||||
u"one-time key and won\'t be stored anywhere in " +
|
||||
self.cc_ledit.setToolTip(_(u"<p>Enter the password " +
|
||||
u"for your B&N account.</p>" +
|
||||
u"<p>The password will only be used to generate this " +
|
||||
u"key and won\'t be stored anywhere in " +
|
||||
u"calibre or on your computer."))
|
||||
ccn_group.addWidget(self.cc_ledit)
|
||||
ccn_disclaimer_label = QLabel(_('(Will not be saved in configuration data)'), self)
|
||||
@@ -563,8 +562,8 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def key_value(self):
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
return generate_bandn_key(self.user_name,self.cc_number)
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as fetch_bandn_key
|
||||
return fetch_bandn_key(self.user_name,self.cc_number)
|
||||
|
||||
@property
|
||||
def user_name(self):
|
||||
@@ -572,16 +571,13 @@ class AddBandNKeyDialog(QDialog):
|
||||
|
||||
@property
|
||||
def cc_number(self):
|
||||
return unicode(self.cc_ledit.text()).strip().replace(' ', '').replace('-','')
|
||||
return unicode(self.cc_ledit.text()).strip()
|
||||
|
||||
|
||||
def accept(self):
|
||||
if len(self.key_name) == 0 or len(self.user_name) == 0 or len(self.cc_number) == 0 or self.key_name.isspace() or self.user_name.isspace() or self.cc_number.isspace():
|
||||
errmsg = u"All fields are required!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if not self.cc_number.isdigit():
|
||||
errmsg = u"Numbers only in the credit card number field!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
if len(self.key_name) < 4:
|
||||
errmsg = u"Key name must be at <i>least</i> 4 characters long!"
|
||||
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
|
||||
|
||||
@@ -18,7 +18,7 @@ from calibre.utils.config import dynamic, config_dir, JSONConfig
|
||||
|
||||
from calibre_plugins.dedrm.__init__ import PLUGIN_NAME, PLUGIN_VERSION
|
||||
from calibre_plugins.dedrm.utilities import (uStrCmp, DETAILED_MESSAGE, parseCustString)
|
||||
from calibre_plugins.dedrm.ignoblekeygen import generate_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.ignoblekeyfetch import fetch_key as generate_bandn_key
|
||||
from calibre_plugins.dedrm.erdr2pml import getuser_key as generate_ereader_key
|
||||
from calibre_plugins.dedrm.adobekey import adeptkeys as retrieve_adept_keys
|
||||
from calibre_plugins.dedrm.kindlekey import kindlekeys as retrieve_kindle_keys
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -13,13 +13,14 @@ from __future__ import with_statement
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -143,7 +144,7 @@ def getNookLogFiles():
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
|
||||
for path in paths:
|
||||
# look for nookStudy log file
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
@@ -199,7 +200,7 @@ def nookkeys(files = []):
|
||||
if fileKeys:
|
||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
||||
keys.extend(fileKeys)
|
||||
return keys
|
||||
return list(set(keys))
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
@@ -209,7 +210,7 @@ def getkey(outpath, files=[]):
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
keyfileout.write(keys[-1])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
|
||||
257
DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py
Normal file
257
DeDRM_calibre_plugin/DeDRM_plugin/ignoblekeyfetch.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.1
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Based on discoveries by "Nobody You Know"
|
||||
# Code partly based on ignoblekeygen.py by several people.
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
||||
# program from the command line (python ignoblekeyfetch.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial version
|
||||
# 1.1 - Try second URL if first one fails
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"ignoblekeyfetch.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 fetch_key(email, password):
|
||||
# change name and CC numbers to utf-8 if unicode
|
||||
if type(email)==unicode:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2, re
|
||||
|
||||
# try the URL from nook for PC
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
if len(found)!=28:
|
||||
# try the URL from android devices
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=hobbes_9.3.50818_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print u"Failed to fetch key."
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"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 email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").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"Fetch", 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):
|
||||
email = self.name.get()
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -2,6 +2,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kgenpids.py
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 2.0 - Fix for non-ascii Windows user names
|
||||
|
||||
import sys
|
||||
import os, csv
|
||||
import binascii
|
||||
@@ -164,7 +171,7 @@ def getKindlePids(rec209, token, serialnum):
|
||||
pids=[]
|
||||
|
||||
if isinstance(serialnum,unicode):
|
||||
serialnum = serialnum.encode('ascii')
|
||||
serialnum = serialnum.encode('utf-8')
|
||||
|
||||
# Compute book PID
|
||||
pidHash = SHA1(serialnum+rec209+token)
|
||||
@@ -190,16 +197,16 @@ def getK4Pids(rec209, token, kindleDatabase):
|
||||
|
||||
try:
|
||||
# Get the Mazama Random number
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex').encode('ascii')
|
||||
MazamaRandomNumber = (kindleDatabase[1])['MazamaRandomNumber'].decode('hex')
|
||||
|
||||
# Get the kindle account token
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex').encode('ascii')
|
||||
kindleAccountToken = (kindleDatabase[1])['kindle.account.tokens'].decode('hex')
|
||||
|
||||
# Get the IDString used to decode the Kindle Info file
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex').encode('ascii')
|
||||
IDString = (kindleDatabase[1])['IDString'].decode('hex')
|
||||
|
||||
# Get the UserName stored when the Kindle Info file was decoded
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex').encode('ascii')
|
||||
UserName = (kindleDatabase[1])['UserName'].decode('hex')
|
||||
|
||||
except KeyError:
|
||||
print u"Keys not found in the database {0}.".format(kindleDatabase[0])
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
#
|
||||
# Currently requires alfcrypto.py which requires the alfcrypto library
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -20,6 +18,7 @@ from __future__ import with_statement
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
# 2.0 - Added comments and extra fix for non-ascii Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -885,6 +884,7 @@ if iswindows:
|
||||
return "AlternateUserName"
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
@@ -1161,10 +1161,10 @@ if iswindows:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
DB = {}
|
||||
return DB
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekey.py
|
||||
# Copyright © 2015 Apprentice Alf
|
||||
# Copyright © 2015 Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Based on kindlekey.py, Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
|
||||
@@ -13,13 +13,14 @@ from __future__ import with_statement
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
# 1.1 - remove duplicates and return last key as single key
|
||||
|
||||
"""
|
||||
Get Barnes & Noble EPUB user key from nook Studio log file
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
__version__ = "1.1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -143,7 +144,7 @@ def getNookLogFiles():
|
||||
paths.add(path)
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
|
||||
for path in paths:
|
||||
# look for nookStudy log file
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
@@ -199,7 +200,7 @@ def nookkeys(files = []):
|
||||
if fileKeys:
|
||||
print u"Found {0} keys in the Nook Study log files".format(len(fileKeys))
|
||||
keys.extend(fileKeys)
|
||||
return keys
|
||||
return list(set(keys))
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
@@ -209,7 +210,7 @@ def getkey(outpath, files=[]):
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
keyfileout.write(keys[-1])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# ignoblekeyfetch.pyw, version 1.0
|
||||
# Copyright © 2015 Apprentice Harper
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
# Based on discoveries by "Nobody You Know"
|
||||
|
||||
# Windows users: Before running this program, you must first install Python.
|
||||
# We recommend ActiveState Python 2.7.X for Windows from
|
||||
# http://www.activestate.com/activepython/downloads.
|
||||
# Then save this script file as ignoblekeyfetch.pyw and double-click on it to run it.
|
||||
#
|
||||
# Mac OS X users: Save this script file as ignoblekeyfetch.pyw. You can run this
|
||||
# program from the command line (python ignoblekeygen.pyw) or by double-clicking
|
||||
# it when it has been associated with PythonLauncher.
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Initial release
|
||||
|
||||
"""
|
||||
Fetch Barnes & Noble EPUB user key from B&N servers using email and password
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "1.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
try:
|
||||
from calibre.constants import iswindows, isosx
|
||||
except:
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'. So use shell32.GetCommandLineArgvW to get sys.argv
|
||||
# as a list of Unicode strings and encode them as utf-8
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"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 fetch_key(email, password):
|
||||
# remove spaces and case from name and CC numbers.
|
||||
if type(email)==unicode:
|
||||
email = email.encode('utf-8')
|
||||
if type(password)==unicode:
|
||||
password = password.encode('utf-8')
|
||||
|
||||
import random
|
||||
random = "%030x" % random.randrange(16**30)
|
||||
|
||||
import urllib, urllib2
|
||||
fetch_url = "https://cart4.barnesandnoble.com/services/service.aspx?Version=2&acctPassword="
|
||||
fetch_url += urllib.quote(password,'')+"&devID=PC_BN_2.5.6.9575_"+random+"&emailAddress="
|
||||
fetch_url += urllib.quote(email,"")+"&outFormat=5&schema=1&service=1&stage=deviceHashB"
|
||||
#print fetch_url
|
||||
|
||||
found = ''
|
||||
try:
|
||||
req = urllib2.Request(fetch_url)
|
||||
response = urllib2.urlopen(req)
|
||||
the_page = response.read()
|
||||
#print the_page
|
||||
|
||||
import re
|
||||
|
||||
found = re.search('ccHash>(.+?)</ccHash', the_page).group(1)
|
||||
except:
|
||||
found = ''
|
||||
return found
|
||||
|
||||
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
if len(argv) != 4:
|
||||
print u"usage: {0} <email> <password> <keyfileout.b64>".format(progname)
|
||||
return 1
|
||||
email, password, keypath = argv[1:]
|
||||
userkey = fetch_key(email, password)
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
return 0
|
||||
print u"Failed to fetch key."
|
||||
return 1
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"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 email address").grid(row=0)
|
||||
self.name = Tkinter.Entry(body, width=40)
|
||||
self.name.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"Account password").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"Fetch", 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):
|
||||
email = self.name.get()
|
||||
password = self.ccn.get()
|
||||
keypath = self.keypath.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email address not given"
|
||||
return
|
||||
if not password:
|
||||
self.status['text'] = u"Account password not given"
|
||||
return
|
||||
if not keypath:
|
||||
self.status['text'] = u"Output keyfile path not set"
|
||||
return
|
||||
self.status['text'] = u"Fetching..."
|
||||
try:
|
||||
userkey = fetch_key(email, password)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: {0}".format(e.args[0])
|
||||
return
|
||||
if len(userkey) == 28:
|
||||
open(keypath,'wb').write(userkey)
|
||||
self.status['text'] = u"Keyfile fetched successfully"
|
||||
else:
|
||||
self.status['text'] = u"Keyfile fetch failed."
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Barnes & Noble ePub Keyfile Fetch v.{0}".format(__version__))
|
||||
root.resizable(True, False)
|
||||
root.minsize(300, 0)
|
||||
DecryptionDialog(root).pack(fill=Tkconstants.X, expand=1)
|
||||
root.mainloop()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
sys.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
@@ -240,6 +240,7 @@ def gui_main():
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
import tkFileDialog
|
||||
import traceback
|
||||
except:
|
||||
return cli_main()
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindlekey.py
|
||||
# Copyright © 2010-2013 by some_updates and Apprentice Alf
|
||||
#
|
||||
# Currently requires alfcrypto.py which requires the alfcrypto library
|
||||
# Copyright © 2010-2015 by some_updates, Apprentice Alf and Apprentice Harper
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||
@@ -20,6 +18,7 @@ from __future__ import with_statement
|
||||
# 1.7 - Work if TkInter is missing
|
||||
# 1.8 - Fixes for Kindle for Mac, and non-ascii in Windows user names
|
||||
# 1.9 - Fixes for Unicode in Windows user names
|
||||
# 2.0 - Added comments and extra fix for non-ascii Windows user names
|
||||
|
||||
|
||||
"""
|
||||
@@ -885,6 +884,7 @@ if iswindows:
|
||||
return "AlternateUserName"
|
||||
buffer = create_unicode_buffer(len(buffer) * 2)
|
||||
size.value = len(buffer)
|
||||
# return low byte of the unicode value of each character of the username
|
||||
return buffer.value.encode('utf-16-le')[::2]
|
||||
return GetUserName
|
||||
GetUserName = GetUserName()
|
||||
@@ -1161,10 +1161,10 @@ if iswindows:
|
||||
DB[keyname] = cleartext
|
||||
|
||||
if 'kindle.account.tokens' in DB:
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().decode("latin-1"))
|
||||
# store values used in decryption
|
||||
DB['IDString'] = GetIDString()
|
||||
DB['UserName'] = GetUserName()
|
||||
print u"Decrypted key file using IDString '{0:s}' and UserName '{1:s}'".format(GetIDString(), GetUserName().encode('hex'))
|
||||
else:
|
||||
DB = {}
|
||||
return DB
|
||||
|
||||
275
Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.pyw
Normal file
275
Other_Tools/DRM_Key_Scripts/Kindle_for_iOS/kindleiospidgen.pyw
Normal file
@@ -0,0 +1,275 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import with_statement
|
||||
|
||||
# kindleforios4key.py
|
||||
# Copyright © 2013 by Apprentice Alf
|
||||
# Portions Copyright © 2007, 2009 Igor Skochinsky <skochinsky@mail.ru>
|
||||
|
||||
# Revision history:
|
||||
# 1.0 - Generates fixed PID for Kindle for iOS 3.1.1 running on iOS 4.x
|
||||
|
||||
|
||||
"""
|
||||
Generate fixed PID for Kindle for iOS 3.1.1
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '1.0'
|
||||
|
||||
import sys, os
|
||||
import getopt
|
||||
import binascii
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
# encoded using "replace" before writing them.
|
||||
class SafeUnbuffered:
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
self.encoding = stream.encoding
|
||||
if self.encoding == None:
|
||||
self.encoding = "utf-8"
|
||||
def write(self, data):
|
||||
if isinstance(data,unicode):
|
||||
data = data.encode(self.encoding,"replace")
|
||||
self.stream.write(data)
|
||||
self.stream.flush()
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.stream, attr)
|
||||
|
||||
iswindows = sys.platform.startswith('win')
|
||||
isosx = sys.platform.startswith('darwin')
|
||||
|
||||
def unicode_argv():
|
||||
if iswindows:
|
||||
# Uses shell32.GetCommandLineArgvW to get sys.argv as a list of Unicode
|
||||
# strings.
|
||||
|
||||
# Versions 2.x of Python don't support Unicode in sys.argv on
|
||||
# Windows, with the underlying Windows API instead replacing multi-byte
|
||||
# characters with '?'.
|
||||
|
||||
|
||||
from ctypes import POINTER, byref, cdll, c_int, windll
|
||||
from ctypes.wintypes import LPCWSTR, LPWSTR
|
||||
|
||||
GetCommandLineW = cdll.kernel32.GetCommandLineW
|
||||
GetCommandLineW.argtypes = []
|
||||
GetCommandLineW.restype = LPCWSTR
|
||||
|
||||
CommandLineToArgvW = windll.shell32.CommandLineToArgvW
|
||||
CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)]
|
||||
CommandLineToArgvW.restype = POINTER(LPWSTR)
|
||||
|
||||
cmd = GetCommandLineW()
|
||||
argc = c_int(0)
|
||||
argv = CommandLineToArgvW(cmd, byref(argc))
|
||||
if argc.value > 0:
|
||||
# Remove Python executable and commands if present
|
||||
start = argc.value - len(sys.argv)
|
||||
return [argv[i] for i in
|
||||
xrange(start, argc.value)]
|
||||
# if we don't have any arguments at all, just pass back script name
|
||||
# this should never happen
|
||||
return [u"mobidedrm.py"]
|
||||
else:
|
||||
argvencoding = sys.stdin.encoding
|
||||
if argvencoding == None:
|
||||
argvencoding = "utf-8"
|
||||
return [arg if (type(arg) == unicode) else unicode(arg,argvencoding) for arg in sys.argv]
|
||||
|
||||
import hashlib
|
||||
|
||||
def SHA256(message):
|
||||
ctx = hashlib.sha256()
|
||||
ctx.update(message)
|
||||
return ctx.digest()
|
||||
|
||||
def crc32(s):
|
||||
return (~binascii.crc32(s,-1))&0xFFFFFFFF
|
||||
|
||||
letters = 'ABCDEFGHIJKLMNPQRSTUVWXYZ123456789'
|
||||
|
||||
def checksumPid(s):
|
||||
crc = crc32(s)
|
||||
crc = crc ^ (crc >> 16)
|
||||
res = s
|
||||
l = len(letters)
|
||||
for i in (0,1):
|
||||
b = crc & 0xff
|
||||
pos = (b // l) ^ (b % l)
|
||||
res += letters[pos%l]
|
||||
crc >>= 8
|
||||
|
||||
return res
|
||||
|
||||
def pidFromSerial(s, l):
|
||||
crc = crc32(s)
|
||||
|
||||
arr1 = [0]*l
|
||||
for i in xrange(len(s)):
|
||||
arr1[i%l] ^= ord(s[i])
|
||||
|
||||
crc_bytes = [crc >> 24 & 0xff, crc >> 16 & 0xff, crc >> 8 & 0xff, crc & 0xff]
|
||||
for i in xrange(l):
|
||||
arr1[i] ^= crc_bytes[i&3]
|
||||
|
||||
pid = ''
|
||||
for i in xrange(l):
|
||||
b = arr1[i] & 0xff
|
||||
pid+=letters[(b >> 7) + ((b >> 5 & 3) ^ (b & 0x1f))]
|
||||
|
||||
return pid
|
||||
|
||||
def generatekeys(email, mac):
|
||||
keys = []
|
||||
email = email.encode('utf-8').lower()
|
||||
mac = mac.encode('utf-8').lower()
|
||||
cleanmac = "".join(c if (c in "0123456789abcdef") else "" for c in mac)
|
||||
lowermac = cleanmac.lower()
|
||||
#print lowermac
|
||||
keyseed = lowermac + email.encode('utf-8')
|
||||
#print keyseed
|
||||
keysha256 = SHA256(keyseed)
|
||||
keybase64 = keysha256.encode('base64')
|
||||
#print keybase64
|
||||
cleankeybase64 = "".join(c if (c in "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") else "0" for c in keybase64)
|
||||
#print cleankeybase64
|
||||
pseudoudid = cleankeybase64[:40]
|
||||
#print pseudoudid
|
||||
keys.append(pidFromSerial(pseudoudid.encode("utf-8"),8))
|
||||
return keys
|
||||
|
||||
# interface for Python DeDRM
|
||||
# returns single key or multiple keys, depending on path or file passed in
|
||||
def getkey(email, mac, outpath):
|
||||
keys = generatekeys(email,mac)
|
||||
if len(keys) > 0:
|
||||
if not os.path.isdir(outpath):
|
||||
outfile = outpath
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(keys[0])
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
else:
|
||||
keycount = 0
|
||||
for key in keys:
|
||||
while True:
|
||||
keycount += 1
|
||||
outfile = os.path.join(outpath,u"kindleios{0:d}.pid".format(keycount))
|
||||
if not os.path.exists(outfile):
|
||||
break
|
||||
with file(outfile, 'w') as keyfileout:
|
||||
keyfileout.write(key)
|
||||
print u"Saved a key to {0}".format(outfile)
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage(progname):
|
||||
print u"Generates the key for Kindle for iOS 3.1.1"
|
||||
print u"Requires email address of Amazon acccount"
|
||||
print u"And MAC address for iOS device’s wifi"
|
||||
print u"Outputs to a file or to stdout"
|
||||
print u"Usage:"
|
||||
print u" {0:s} [-h] <email address> <MAC address> [<outfile>]".format(progname)
|
||||
|
||||
|
||||
def cli_main():
|
||||
sys.stdout=SafeUnbuffered(sys.stdout)
|
||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||
argv=unicode_argv()
|
||||
progname = os.path.basename(argv[0])
|
||||
print u"{0} v{1}\nCopyright © 2013 Apprentice Alf".format(progname,__version__)
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv[1:], "h")
|
||||
except getopt.GetoptError, err:
|
||||
print u"Error in options or arguments: {0}".format(err.args[0])
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == "-h":
|
||||
usage(progname)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if len(args) < 2 or len(args) > 3:
|
||||
usage(progname)
|
||||
sys.exit(2)
|
||||
|
||||
if len(args) == 3:
|
||||
# save to the specified file or folder
|
||||
getkey(args[0],args[1],args[2])
|
||||
else:
|
||||
keys = generatekeys(args[0],args[1])
|
||||
for key in keys:
|
||||
print key
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def gui_main():
|
||||
try:
|
||||
import Tkinter
|
||||
import Tkconstants
|
||||
import tkMessageBox
|
||||
except:
|
||||
print "Tkinter not installed"
|
||||
return cli_main()
|
||||
|
||||
class DecryptionDialog(Tkinter.Frame):
|
||||
def __init__(self, root):
|
||||
Tkinter.Frame.__init__(self, root, border=5)
|
||||
self.status = Tkinter.Label(self, text=u"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"Amazon email address").grid(row=0)
|
||||
self.email = Tkinter.Entry(body, width=40)
|
||||
self.email.grid(row=0, column=1, sticky=sticky)
|
||||
Tkinter.Label(body, text=u"iOS MAC address").grid(row=1)
|
||||
self.mac = Tkinter.Entry(body, width=40)
|
||||
self.mac.grid(row=1, column=1, sticky=sticky)
|
||||
buttons = Tkinter.Frame(self)
|
||||
buttons.pack()
|
||||
button = Tkinter.Button(
|
||||
buttons, text=u"Generate", width=10, command=self.generate)
|
||||
button.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 generate(self):
|
||||
email = self.email.get()
|
||||
mac = self.mac.get()
|
||||
if not email:
|
||||
self.status['text'] = u"Email not specified"
|
||||
return
|
||||
if not mac:
|
||||
self.status['text'] = u"MAC not specified"
|
||||
return
|
||||
self.status['text'] = u"Generating..."
|
||||
try:
|
||||
keys = generatekeys(email, mac)
|
||||
except Exception, e:
|
||||
self.status['text'] = u"Error: (0}".format(e.args[0])
|
||||
return
|
||||
self.status['text'] = ", ".join(key for key in keys)
|
||||
|
||||
root = Tkinter.Tk()
|
||||
root.title(u"Kindle for iOS PID 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.exit(cli_main())
|
||||
sys.exit(gui_main())
|
||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# DeDRM_tools
|
||||
DeDRM tools for ebooks
|
||||
|
||||
This is a repository of all the scripts and other tools for removing DRM from ebooks that I could find, commited in date order as best as I could manage. (Except for the Requiem tools for Apple's iBooks, and Convert LIT for Microsoft's .lit ebooks.)
|
||||
|
||||
Mostly it tracks the tools releases by Apprentice Alf, athough it also includes the individual tools and their histories from before Alf had a blog.
|
||||
|
||||
Users should download the latest zip archive.
|
||||
Developers might be interested in forking the repository, as it contains unzipped versions of those tools that are zipped, and text versions of the AppleScripts, to make the changes over time easier to follow.
|
||||
|
||||
I welcome contributions from others to improve these tools, from expanding the range of books handled, improving key retrieval, to just general bug fixe, speed improvements and UI enhancements.
|
||||
|
||||
My special thanks to all those developers who have done the hard work of reverse engineering to provide the initial tools.
|
||||
|
||||
Apprentice Harper.
|
||||
@@ -1,12 +1,12 @@
|
||||
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 v6.2.0 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
|
||||
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 v6.2.2 archive from Apprentice Alf's Blog: http://apprenticealf.wordpress.com/
|
||||
|
||||
The is archive includes tools to remove DRM from:
|
||||
|
||||
- Kindle ebooks (Mobi, Topaz, Print Replica and KF8).
|
||||
- Barnes and Noble ePubs downloaded through NOOK Study
|
||||
- Barnes and Noble ePubs
|
||||
- Adobe Digital Editions ePubs (including Kobo and Google ePubs downloaded to ADE)
|
||||
- Kobo kePubs from the Kobo Desktop application
|
||||
- Adobe Digital Editions PDFs
|
||||
@@ -19,13 +19,13 @@ These tools do NOT work with Apple's iBooks FairPlay DRM (see end of this file.)
|
||||
|
||||
About the tools
|
||||
---------------
|
||||
These tools are updated and maintained by Apprentice Alf and Apprentice Harper. You can find the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
These tools are updated and maintained by Apprentice Alf and Apprentice Harper. You can find links to the latest updates and get support at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
If you re-post these tools, a link to the blog would be appreciated.
|
||||
|
||||
|
||||
DeDRM plugin for calibre (Mac OS X, Windows, and Linux)
|
||||
-------------------------------------------------------
|
||||
If you already use calibre, the quickest and easiest way, especially on Windows, to remove DRM from your ebooks is to install the DeDRM plugin from the DeDRM_calibre_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
|
||||
Calibre is an open source freeware ebook library manager. It is the best tool around for keeping track of your ebooks. The DeDRM plugin for calibre provides the simplest way, especially on Windows, to remove DRM from your ebooks. Just install the DeDRM plugin from the DeDRM_calibre_plugin folder, following the instructions and configuration directions provided in the ReadMe and the help links.
|
||||
|
||||
Once installed and configured, you can simply add a DRM book to calibre and the DeDRMed version will be imported into the calibre database. Note that DRM removal only occurs on IMPORT not on CONVERSION or at any other time, not even conversion to other formats. If you have already imported DRM books you'll need to remove them from calibre and re-import them.
|
||||
|
||||
@@ -38,7 +38,7 @@ This application is a stand-alone DRM removal application for Mac OS X users.
|
||||
|
||||
For instructions, see the "DeDRM ReadMe.rtf" file in the DeDRM_Application_Macintosh folder.
|
||||
|
||||
N.B. Mac OS X 10.4 users need to take extra steps befor using the application, see the ReadMe.
|
||||
N.B. Mac OS X 10.4 users need to take extra steps before using the application, see the ReadMe.
|
||||
|
||||
|
||||
DeDRM application for Windows users: (Windows XP through Windows 8)
|
||||
@@ -60,13 +60,13 @@ For instructions, see the obok_plugin_ReadMe.txt file in the Obok_calibre_plugin
|
||||
|
||||
Other_Tools
|
||||
-----------
|
||||
This is folder other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
|
||||
|
||||
B&N_Download_Helper
|
||||
A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Another one only for the adventurous.
|
||||
This is a folder of other tools that may be useful for DRMed ebooks from certain sources or for Linux users. Most users won't need any of these tools.
|
||||
|
||||
DRM_Key_Scripts
|
||||
This folder contains python scripts that create or extract encryption keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs and Kindle for Mac/PC ebooks.
|
||||
This folder contains python scripts that create or extract or fetch encryption keyfiles for Barnes and Noble ePubs, Adobe Digital Editions ePubs, Kindle for Mac/PC and Kindle for Android ebooks. These files are needed for the Windows stand-alone DeDRM application.
|
||||
|
||||
B&N_Download_Helper
|
||||
A Javascript to enable a download button at the B&N website for ebooks that normally won't download to your PC. Only for the adventurous.
|
||||
|
||||
Kindle_for_Android_Patches
|
||||
Definitely only for the adventurous, this folder contains information on how to modify the Kindel for Android app to b able to get a PID for use with the other Kindle tools (DeDRM apps and calibre plugin).
|
||||
|
||||
Reference in New Issue
Block a user