print("{0} v{1}: Exception when getting default NOOK Microsoft App keys after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
traceback.print_exc()
+ ###### Add keys from Adobe PassHash ADE activation data (adobekey_get_passhash.py)
+
+ try:
+ if iswindows:
+ # Right now this is only implemented for Windows. MacOS support still needs to be added.
+ from calibre_plugins.dedrm.adobekey_get_passhash import passhash_keys
+ defaultkeys_ade, names = passhash_keys()
+ if isosx:
+ print("{0} v{1}: Dumping ADE PassHash data is not yet supported on MacOS.".format(PLUGIN_NAME, PLUGIN_VERSION))
+ except:
+ print("{0} v{1}: Exception when getting PassHashes from ADE after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
+ traceback.print_exc()
+
###### Check if one of the new keys decrypts the book:
if keyvalue not in dedrmprefs['bandnkeys'].values() and keyvalue not in newkeys:
newkeys.append(keyvalue)
+ for keyvalue in defaultkeys_ade:
+ if keyvalue not in dedrmprefs['bandnkeys'].values() and keyvalue not in newkeys:
+ newkeys.append(keyvalue)
+
if len(newkeys) > 0:
try:
for i,userkey in enumerate(newkeys):
# Store the new successful key in the defaults
print("{0} v{1}: Saving a new default key".format(PLUGIN_NAME, PLUGIN_VERSION))
try:
- dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_key_'+time.strftime("%Y-%m-%d"),keyvalue)
+ if userkey in defaultkeys_ade:
+ dedrmprefs.addnamedvaluetoprefs('bandnkeys','ade_passhash_'+str(int(time.time())),keyvalue)
+ else:
+ dedrmprefs.addnamedvaluetoprefs('bandnkeys','nook_key_'+str(int(time.time())),keyvalue)
dedrmprefs.writeprefs()
print("{0} v{1}: Saved a new default key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime))
except:
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# adobekey_get_passhash.py, version 1
+# based on adobekey.pyw, version 7.2
+# Copyright © 2009-2021 i♥cabbages, Apprentice Harper et al.
+# Copyright © 2021 noDRM
+
+# Released under the terms of the GNU General Public Licence, version 3
+# <http://www.gnu.org/licenses/>
+
+# Revision history:
+# 1 - Initial release
+
+"""
+Retrieve Adobe ADEPT user passhash keys
+"""
+
+__license__ = 'GPL v3'
+__version__ = '1'
+
+import sys, os, time
+import base64, hashlib
+try:
+ from Cryptodome.Cipher import AES
+except:
+ from Crypto.Cipher import AES
+
+PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
+
+def unpad(data):
+
+ if sys.version_info[0] == 2:
+ pad_len = ord(data[-1])
+ else:
+ pad_len = data[-1]
+
+ return data[:-pad_len]
+
+
+try:
+ from calibre.constants import iswindows, isosx
+except:
+ iswindows = sys.platform.startswith('win')
+ isosx = sys.platform.startswith('darwin')
+
+
+class ADEPTError(Exception):
+ pass
+
+def decrypt_passhash(passhash, fp):
+
+ serial_number = base64.b64decode(fp).hex()
+
+ hash_key = hashlib.sha1(bytearray.fromhex(serial_number + PASS_HASH_SECRET)).digest()[:16]
+
+ encrypted_cc_hash = base64.b64decode(passhash)
+ cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
+ return base64.b64encode(cc_hash).decode("ascii")
+
+
+if iswindows:
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg
+
+ PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
+
+ def passhash_keys():
+ cuser = winreg.HKEY_CURRENT_USER
+ keys = []
+ names = []
+ try:
+ plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
+ except WindowsError:
+ raise ADEPTError("Could not locate ADE activation")
+
+ idx = 1
+
+ fp = None
+
+ i = -1
+ while True:
+ i = i + 1 # start with 0
+ try:
+ plkparent = winreg.OpenKey(plkroot, "%04d" % (i,))
+ except:
+ # No more keys
+ break
+
+ ktype = winreg.QueryValueEx(plkparent, None)[0]
+
+ if ktype == "activationToken":
+ # find fingerprint for hash decryption
+ j = -1
+ while True:
+ j = j + 1 # start with 0
+ try:
+ plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
+ except WindowsError:
+ break
+ ktype = winreg.QueryValueEx(plkkey, None)[0]
+ if ktype == 'fingerprint':
+ fp = winreg.QueryValueEx(plkkey, 'value')[0]
+ #print("Found fingerprint: " + fp)
+
+ if ktype == 'passHashList':
+
+ j = -1
+ lastOperator = "Unknown"
+ while True:
+ j = j + 1 # start with 0
+ try:
+ plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
+ except WindowsError:
+ break
+ ktype = winreg.QueryValueEx(plkkey, None)[0]
+ if ktype == 'operatorURL':
+ operatorURL = winreg.QueryValueEx(plkkey, 'value')[0]
+ #print("Found operator URL: " + operatorURL)
+ try:
+ lastOperator = operatorURL.split('//')[1].split('/')[0]
+ except:
+ lastOperator = "Unknown"
+
+ elif ktype == "passHash":
+ passhash_encrypted = winreg.QueryValueEx(plkkey, 'value')[0]
+ names.append("ADE_key_" + lastOperator + "_" + str(int(time.time())) + "_" + str(idx))
+ idx = idx + 1
+ keys.append(passhash_encrypted)
+
+ if fp is None:
+ #print("Didn't find fingerprint for decryption ...")
+ return [], []
+
+ print("Found {0:d} passhashes".format(len(keys)))
+
+ keys_decrypted = []
+
+ for key in keys:
+ decrypted = decrypt_passhash(key, fp)
+ #print("Input key: " + key)
+ #print("Output key: " + decrypted)
+ keys_decrypted.append(decrypted)
+
+ return keys_decrypted, names
+
+
+else:
+ def passhash_keys():
+ raise ADEPTError("This script only supports Windows.")
+ #TODO: Add MacOS support by parsing the activation.xml file.
+ return [], []
+
+
+if __name__ == '__main__':
+ print("This is a python calibre plugin. It can't be directly executed.")
if d.result() != d.Accepted:
# New key generation cancelled.
return
- new_key_value = d.key_value
- if type(self.plugin_keys) == dict:
- if new_key_value in self.plugin_keys.values():
- old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
- info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
- "The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
- return
- self.plugin_keys[d.key_name] = new_key_value
+
+ if d.k_key_list is not None:
+ # importing multiple keys
+ idx = -1
+ dup_key_count = 0
+ added_key_count = 0
+
+ while True:
+ idx = idx + 1
+ try:
+ new_key_value = d.k_key_list[idx]
+ except:
+ break
+
+ if type(self.plugin_keys) == dict:
+ if new_key_value in self.plugin_keys.values():
+ dup_key_count = dup_key_count + 1
+ continue
+ print("Setting idx " + str(idx) + ", name " + d.k_name_list[idx] + " to " + new_key_value)
+ self.plugin_keys[d.k_name_list[idx]] = new_key_value
+ added_key_count = added_key_count + 1
+ else:
+ if new_key_value in self.plugin_keys:
+ dup_key_count = dup_key_count + 1
+ continue
+ self.plugin_keys.append(new_key_value)
+ added_key_count = added_key_count + 1
+
+
+ if (added_key_count > 0):
+ info_dialog(None, "{0} {1}: Adding {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
+ "Successfully added {0} key(s).".format(added_key_count), show=True)
+
else:
- if new_key_value in self.plugin_keys:
- info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
- "This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
- return
+ # Import single key
+ new_key_value = d.key_value
+ if type(self.plugin_keys) == dict:
+ if new_key_value in self.plugin_keys.values():
+ old_key_name = [name for name, value in self.plugin_keys.items() if value == new_key_value][0]
+ info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
+ "The new {1} is the same as the existing {1} named <strong>{0}</strong> and has not been added.".format(old_key_name,self.key_type_name), show=True)
+ return
+ self.plugin_keys[d.key_name] = new_key_value
+ else:
+ if new_key_value in self.plugin_keys:
+ info_dialog(None, "{0} {1}: Duplicate {2}".format(PLUGIN_NAME, PLUGIN_VERSION,self.key_type_name),
+ "This {0} is already in the list of {0}s has not been added.".format(self.key_type_name), show=True)
+ return
+
+ self.plugin_keys.append(d.key_value)
- self.plugin_keys.append(d.key_value)
self.listy.clear()
self.populate_list()
self.add_fields_for_passhash()
elif idx == 2:
self.add_fields_for_b64_passhash()
- elif idx == 3:
- self.add_fields_for_windows_nook()
+ elif idx == 3:
+ self.add_fields_for_ade_passhash()
elif idx == 4:
+ self.add_fields_for_windows_nook()
+ elif idx == 5:
self.add_fields_for_android_nook()
+
+ def add_fields_for_ade_passhash(self):
+
+ self.ade_extr_group_box = QGroupBox("", self)
+ ade_extr_group_box_layout = QVBoxLayout()
+ self.ade_extr_group_box.setLayout(ade_extr_group_box_layout)
+
+ self.layout.addWidget(self.ade_extr_group_box)
+
+ ade_extr_group_box_layout.addWidget(QLabel("Click \"OK\" to try and dump PassHash data \nfrom Adobe Digital Editions. This works if\nyou've opened your PassHash books in ADE before.", self))
+
+ self.button_box.hide()
+
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.accept_ade_dump_passhash)
+ self.button_box.rejected.connect(self.reject)
+ self.layout.addWidget(self.button_box)
+
+ self.resize(self.sizeHint())
+
+
def add_fields_for_android_nook(self):
self.andr_nook_group_box = QGroupBox("", self)
self.cbType.addItem("--- Select key type ---")
self.cbType.addItem("Adobe PassHash username & password")
self.cbType.addItem("Base64-encoded PassHash key string")
+ self.cbType.addItem("Extract passhashes from Adobe Digital Editions")
self.cbType.addItem("Extract key from Nook Windows application")
self.cbType.addItem("Extract key from Nook Android application")
self.cbType.currentIndexChanged.connect(self.update_form, self.cbType.currentIndex())
@property
def key_name(self):
- return str(self.key_ledit.text()).strip()
+ try:
+ return str(self.key_ledit.text()).strip()
+ except:
+ return self.result_data_name
@property
def key_value(self):
def cc_number(self):
return str(self.cc_ledit.text()).strip()
+
+ @property
+ def k_name_list(self):
+ # If the plugin supports returning multiple keys, return a list of names.
+ if self.k_full_name_list is not None and self.k_full_key_list is not None:
+ return self.k_full_name_list
+ return None
+
+ @property
+ def k_key_list(self):
+ # If the plugin supports returning multiple keys, return a list of keys.
+ if self.k_full_name_list is not None and self.k_full_key_list is not None:
+ return self.k_full_key_list
+ return None
+
+
+
def accept_android_nook(self):
if len(self.key_name) < 4:
errmsg = "Failed to extract keys. Is this the correct folder?"
return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ # Take the first key we found. In the future it might be a good idea to import them all.
+ # See accept_ade_dump_passhash for an example on how to do that.
self.result_data = store_result[0]
QDialog.accept(self)
+ def accept_ade_dump_passhash(self):
+
+ try:
+ from calibre_plugins.dedrm.adobekey_get_passhash import passhash_keys
+ keys, names = passhash_keys()
+ except:
+ errmsg = "Failed to grab PassHash keys from ADE."
+ return error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+
+ # Take the first new key we found.
+
+ idx = -1
+ new_keys = []
+ new_names = []
+ for key in keys:
+ idx = idx + 1
+ if key in self.parent.plugin_keys.values():
+ continue
+
+ new_keys.append(key)
+ new_names.append(names[idx])
+
+ if len(new_keys) == 0:
+ # Okay, we didn't find anything. How do we get rid of the window?
+ errmsg = "Didn't find any PassHash keys in ADE."
+ error_dialog(None, "{0} {1}".format(PLUGIN_NAME, PLUGIN_VERSION), errmsg, show=True, show_copy_button=False)
+ QDialog.reject(self)
+ return
+
+ # Add new keys to list.
+ self.k_full_name_list = new_names
+ self.k_full_key_list = new_keys
+ QDialog.accept(self)
+ return
+
def accept_win_nook(self):
from calibre_plugins.dedrm.ignoblekeyNookStudy import nookkeys
store_result = nookkeys()
- # Take the first key we found. In the future it might be a good idea to import them all,
- # but with how the import dialog is currently structured that's not easily possible.
+ # Take the first key we found. In the future it might be a good idea to import them all.
+ # See accept_ade_dump_passhash for an example on how to do that.
if len(store_result) > 0:
self.result_data = store_result[0]
QDialog.accept(self)
# Right now this code only supports adding one key per each invocation,
# so if the user has multiple keys, he's going to need to add the "plus" button multiple times.
+ # In the future it might be a good idea to import them all.
+ # See accept_ade_dump_passhash for an example on how to do that.
+
if len(self.new_keys)>0:
self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)