mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2026-03-20 04:58:56 +00:00
Compare commits
14 Commits
a4689f6ac0
...
v10.0.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
077e8f5c2a | ||
|
|
fed8bb716b | ||
|
|
c12d214b59 | ||
|
|
012ff533ab | ||
|
|
dcbb377566 | ||
|
|
76ce6d9c5c | ||
|
|
726d72217e | ||
|
|
2d51005cf1 | ||
|
|
7eb8f07a33 | ||
|
|
e4fe032e47 | ||
|
|
bb170688ba | ||
|
|
b283777c0a | ||
|
|
cf095a4171 | ||
|
|
263cc1d2cf |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -35,7 +35,7 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||
- Fix small issue with elibri watermark removal.
|
||||
- Adobe key name will now contain account email.
|
||||
|
||||
## Fixes on master (not yet released):
|
||||
## Fixes in v10.0.3 (2022-07-13):
|
||||
|
||||
- Fix issue where importing a key from Adobe Digital Editions would fail in Python2 (Calibre < 5) if there were non-ASCII characters in the username.
|
||||
- Add code to support importing multiple decryption keys from ADE.
|
||||
@@ -56,9 +56,17 @@ List of changes since the fork of Apprentice Harper's repository:
|
||||
- Drop support for importing key data from the ancient, pre "DeDRM" Calibre plugins ("Ignoble Epub DeDRM", "eReader PDB 2 PML" and "K4MobiDeDRM"). These are from 2011, I doubt anyone still has these installed, I can't even find a working link for these to test them. If you still have encryption keys in one of these plugins, you will need to update to DeDRM v10.0.2 or older (to convert the keys) before updating to DeDRM v10.0.3 or newer.
|
||||
- Some Python3 bugfixes for Amazon books (merged #10 by ableeker).
|
||||
- Fix a bug where extracting an Adobe key from ADE on Linux through Wine did fail when using the OpenSSL backend (instead of PyCrypto). See #13 and #14 for details, thanks acaloiaro for the bugfix.
|
||||
- Make the plugin work on Calibre 6 (Qt 6). If you're running the Calibre 6 beta and you notice any issues, please open a bug report.
|
||||
- Fix IndexError when DeDRMing some Amazon eBooks.
|
||||
- Add support for books with the new ADE3.0+ DRM by merging #48 by a980e066a01. Thanks a lot!
|
||||
- Add support for books with the new ADE3.0+ DRM by merging #48 by a980e066a01. Thanks a lot! (Also fixes #96 on MacOS)
|
||||
- Remove OpenSSL support, now the plugin will always use the Python crypto libraries.
|
||||
- Obok: Fix issues with invalid UTF-8 characters by merging #26 by baby-bell.
|
||||
- Try to fix PDF files with V3 key obfuscation algorithm.
|
||||
- ineptpdf: Fix broken V=3 key obfuscation algorithm.
|
||||
- ineptpdf: (Hopefully) fix issues with some B&N PDF files.
|
||||
- Fix broken Amazon K4PC key retrieval (fixes #38)
|
||||
- Fix bug that corrupts output file for Print-Replica Amazon books (fixes #30).
|
||||
- Fix Nook Study key retrieval code (partially fixes #50).
|
||||
- Make the plugin work on Calibre 6 (Qt 6). (fixes #54 and #98) If you're running Calibre 6 and you notice any issues, please open a bug report.
|
||||
|
||||
## Fixes on master (not yet released):
|
||||
|
||||
- (None)
|
||||
@@ -17,7 +17,7 @@ p {margin-top: 0}
|
||||
|
||||
<body>
|
||||
|
||||
<h1>DeDRM Plugin <span class="version">(v10.0.2)</span></h1>
|
||||
<h1>DeDRM Plugin <span class="version">(v10.0.3)</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,14 +39,14 @@ p {margin-top: 0}
|
||||
|
||||
<h3>Troubleshooting:</h3>
|
||||
|
||||
<p >If you find that it’s not working for you , you can save a lot of time by trying to add the ebook to Calibre in debug mode. This will print out a lot of helpful info that can be copied into any online help requests.</p>
|
||||
<p>If you find that it’s not working for you , you can save a lot of time by trying to add the ebook to Calibre in debug mode. This will print out a lot of helpful info that can be copied into any online help requests.</p>
|
||||
|
||||
<p>Open a command prompt (terminal window) and type "calibre-debug -g" (without the quotes). Calibre will launch, and you can can add the problem ebook the usual way. The debug info will be output to the original command prompt (terminal window). Copy the resulting output and paste it into the comment you make at my blog.</p>
|
||||
<p><span class="bold">Note:</span> The Mac version of Calibre doesn’t install the command line tools by default. If you go to the ‘Preferences’ page and click on the miscellaneous button, you’ll find the option to install the command line tools.</p>
|
||||
|
||||
<h3>Credits:</h3>
|
||||
<ul>
|
||||
<li>NoDRM for a bunch of updates and maintenance since November 2021, and the Readium LCP support</li>
|
||||
<li>NoDRM for a bunch of updates and maintenance since November 2021<s>, and the Readium LCP support</s></li>
|
||||
<li>The Dark Reverser for the Mobipocket and eReader scripts</li>
|
||||
<li>i♥cabbages for the Adobe Digital Editions scripts</li>
|
||||
<li>Skindle aka Bart Simpson for the Amazon Kindle for PC script</li>
|
||||
|
||||
@@ -96,8 +96,14 @@ import traceback
|
||||
|
||||
try:
|
||||
import __version
|
||||
except ModuleNotFoundError:
|
||||
except:
|
||||
print("#############################")
|
||||
print("Failed to load the DeDRM plugin")
|
||||
print("Did you bundle this from source code yourself? If so, you'll need to run make_release.py instead to generate a valid plugin file.")
|
||||
print("If you have no idea what the above means, please redownload the most recent version of the plugin from the Github Releases page.")
|
||||
print("If you still receive this error with the released version, please open a bug report and attach the following information:")
|
||||
print("#############################")
|
||||
print("Debug information:")
|
||||
print("__version not found, path is:")
|
||||
print(sys.path)
|
||||
print("I'm at:")
|
||||
@@ -158,7 +164,7 @@ PLUGIN_VERSION_TUPLE = __version.PLUGIN_VERSION_TUPLE
|
||||
|
||||
class DeDRM(FileTypePlugin):
|
||||
name = PLUGIN_NAME
|
||||
description = "Removes DRM from Amazon Kindle, Adobe Adept (including Kobo), Readium LCP, Barnes & Noble, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
||||
description = "Removes DRM from Adobe Adept (including Kobo), Barnes & Noble, Amazon Kindle, Mobipocket and eReader ebooks. Credit given to i♥cabbages and The Dark Reverser for the original stand-alone scripts."
|
||||
supported_platforms = ['linux', 'osx', 'windows']
|
||||
author = "Apprentice Alf, Apprentice Harper, NoDRM, The Dark Reverser and i♥cabbages"
|
||||
version = PLUGIN_VERSION_TUPLE
|
||||
@@ -376,7 +382,7 @@ class DeDRM(FileTypePlugin):
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,"ignoblekeyNookStudy.py")
|
||||
defaultkeys_study = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
|
||||
defaultkeys_study, defaultnames_study = WineGetKeys(scriptpath, ".b64",dedrmprefs['adobewineprefix'])
|
||||
|
||||
except:
|
||||
print("{0} v{1}: Exception when getting default NOOK Study Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
@@ -942,32 +948,39 @@ class DeDRM(FileTypePlugin):
|
||||
from kindlekey import kindlekeys
|
||||
|
||||
defaultkeys = kindlekeys()
|
||||
defaultnames = []
|
||||
else: # linux
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(self.alfdir,"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
|
||||
defaultkeys, defaultnames = WineGetKeys(scriptpath, ".k4i",dedrmprefs['kindlewineprefix'])
|
||||
except:
|
||||
print("{0} v{1}: Exception when getting default Kindle Key after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime))
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
newkeys = {}
|
||||
newnames = []
|
||||
|
||||
for i,keyvalue in enumerate(defaultkeys):
|
||||
keyname = "default_key_{0:d}".format(i+1)
|
||||
if keyvalue not in dedrmprefs['kindlekeys'].values():
|
||||
newkeys[keyname] = keyvalue
|
||||
newkeys["key_{0:d}".format(i)] = keyvalue
|
||||
|
||||
if len(newkeys) > 0:
|
||||
print("{0} v{1}: Found {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
||||
try:
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,list(newkeys.items()),[],[],[],self.starttime)
|
||||
book = k4mobidedrm.GetDecryptedBook(path_to_ebook,newkeys.items(),[],[],[],self.starttime)
|
||||
decoded = True
|
||||
# store the new successful keys in the defaults
|
||||
print("{0} v{1}: Saving {2} new {3}".format(PLUGIN_NAME, PLUGIN_VERSION, len(newkeys), "key" if len(newkeys)==1 else "keys"))
|
||||
i = 1
|
||||
for keyvalue in newkeys.values():
|
||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys','default_key',keyvalue)
|
||||
while "kindle_key_{0:d}_{1:d}".format(int(time.time()), i) in dedrmprefs['kindlekeys']:
|
||||
i = i + 1
|
||||
dedrmprefs.addnamedvaluetoprefs('kindlekeys',"kindle_key_{0:d}_{1:d}".format(int(time.time()), i),keyvalue)
|
||||
dedrmprefs.writeprefs()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
if not decoded:
|
||||
#if you reached here then no luck raise and exception
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
PLUGIN_NAME = "DeDRM"
|
||||
__version__ = '10.0.2'
|
||||
__version__ = '10.0.3'
|
||||
|
||||
PLUGIN_VERSION_TUPLE = tuple([int(x) for x in __version__.split(".")])
|
||||
PLUGIN_VERSION = ".".join([str(x)for x in PLUGIN_VERSION_TUPLE])
|
||||
|
||||
@@ -39,7 +39,7 @@ Retrieve Adobe ADEPT user key.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '7.3'
|
||||
__version__ = '7.4'
|
||||
|
||||
import sys, os, struct, getopt
|
||||
from base64 import b64decode
|
||||
@@ -128,10 +128,16 @@ if iswindows:
|
||||
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||
@@ -381,7 +387,7 @@ if iswindows:
|
||||
pass
|
||||
if ktype == 'privateLicenseKey':
|
||||
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||
userkey = unpad(AES.new(keykey, AES.MODE_CBC, b'\x00'*16).decrypt(b64decode(userkey)), 16)[26:]
|
||||
userkey = unpad(AES.new(keykey, AES.MODE_CBC, b'\x00'*16).decrypt(b64decode(userkey)))[26:]
|
||||
# print ("found " + uuid_name + " key: " + str(userkey))
|
||||
keys.append(userkey)
|
||||
|
||||
|
||||
@@ -23,10 +23,17 @@ import sys, os, time
|
||||
import base64, hashlib
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||
|
||||
@@ -48,7 +55,7 @@ def decrypt_passhash(passhash, fp):
|
||||
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:]), 16)
|
||||
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")
|
||||
|
||||
|
||||
|
||||
@@ -36,10 +36,8 @@ from binascii import a2b_hex, b2a_hex
|
||||
|
||||
try:
|
||||
from Cryptodome.Cipher import AES, DES
|
||||
from Cryptodome.Util.Padding import pad, unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES, DES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
|
||||
# Routines common to Mac and PC
|
||||
|
||||
@@ -116,6 +114,20 @@ STORAGE = "backup.ab"
|
||||
STORAGE1 = "AmazonSecureStorage.xml"
|
||||
STORAGE2 = "map_data_storage.db"
|
||||
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
def pad(data, padding_len=16):
|
||||
padding_data_len = padding_len - (len(data) % padding_len)
|
||||
plaintext = data + chr(padding_data_len) * padding_data_len
|
||||
return plaintext
|
||||
|
||||
class AndroidObfuscation(object):
|
||||
'''AndroidObfuscation
|
||||
For the key, it's written in java, and run in android dalvikvm
|
||||
|
||||
@@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
# Python 3, September 2020
|
||||
|
||||
# Standard Python modules.
|
||||
import sys, os, traceback, json, codecs, base64
|
||||
import sys, os, traceback, json, codecs, base64, time
|
||||
|
||||
from PyQt5.Qt import (Qt, QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit,
|
||||
QGroupBox, QPushButton, QListWidget, QListWidgetItem, QCheckBox,
|
||||
@@ -1237,7 +1237,7 @@ class AddKindleDialog(QDialog):
|
||||
from wineutils import WineGetKeys
|
||||
|
||||
scriptpath = os.path.join(parent.parent.alfdir,"kindlekey.py")
|
||||
defaultkeys = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
|
||||
defaultkeys, defaultnames = WineGetKeys(scriptpath, ".k4i",parent.getwineprefix())
|
||||
|
||||
self.default_key = defaultkeys[0]
|
||||
except:
|
||||
@@ -1255,7 +1255,7 @@ class AddKindleDialog(QDialog):
|
||||
key_group = QHBoxLayout()
|
||||
data_group_box_layout.addLayout(key_group)
|
||||
key_group.addWidget(QLabel("Unique Key Name:", self))
|
||||
self.key_ledit = QLineEdit("default_key", self)
|
||||
self.key_ledit = QLineEdit("default_key_" + str(int(time.time())), self)
|
||||
self.key_ledit.setToolTip("<p>Enter an identifying name for the current default Kindle for Mac/PC key.")
|
||||
key_group.addWidget(self.key_ledit)
|
||||
|
||||
|
||||
@@ -10,13 +10,19 @@ import os
|
||||
import base64
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
import hashlib
|
||||
from lxml import etree
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
|
||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||
|
||||
@@ -46,10 +52,13 @@ def dump_keys(path_to_adobe_folder):
|
||||
hashes = []
|
||||
|
||||
for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
|
||||
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
||||
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]), 16)
|
||||
hashes.append(base64.b64encode(cc_hash).decode("ascii"))
|
||||
#print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
||||
try:
|
||||
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
||||
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
|
||||
hashes.append(base64.b64encode(cc_hash).decode("ascii"))
|
||||
#print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
||||
except:
|
||||
pass
|
||||
|
||||
return hashes
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ def getNookLogFiles():
|
||||
logpath = path +'\\Barnes & Noble\\NOOKstudy\\logs\\BNClientLog.txt'
|
||||
if os.path.isfile(logpath):
|
||||
found = True
|
||||
print('Found nookStudy log file: ' + logpath.encode('ascii','ignore'), file=sys.stderr)
|
||||
print('Found nookStudy log file: ' + logpath, file=sys.stderr)
|
||||
logFiles.append(logpath)
|
||||
else:
|
||||
home = os.getenv('HOME')
|
||||
|
||||
@@ -16,13 +16,19 @@ import base64
|
||||
import traceback
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
import hashlib
|
||||
from lxml import etree
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
|
||||
NOOK_DATA_FOLDER = "%LOCALAPPDATA%\\Packages\\BarnesNoble.Nook_ahnzqzva31enc\\LocalState"
|
||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||
|
||||
@@ -56,11 +56,18 @@ import hashlib
|
||||
try:
|
||||
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
||||
from Cryptodome.PublicKey import RSA
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
|
||||
@@ -50,13 +50,14 @@
|
||||
# 9.1.0 - Support for decrypting with owner password, support for V=5, R=5 and R=6 PDF files, support for AES256-encrypted PDFs.
|
||||
# 9.1.1 - Only support PyCryptodome; clean up the code
|
||||
# 10.0.0 - Add support for "hardened" Adobe DRM (RMSDK >= 10)
|
||||
# 10.0.2 - Fix some Python2 stuff
|
||||
|
||||
"""
|
||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "10.0.0"
|
||||
__version__ = "10.0.2"
|
||||
|
||||
import codecs
|
||||
import hashlib
|
||||
@@ -65,6 +66,8 @@ import os
|
||||
import re
|
||||
import zlib
|
||||
import struct
|
||||
import binascii
|
||||
import base64
|
||||
from io import BytesIO
|
||||
from decimal import Decimal
|
||||
import itertools
|
||||
@@ -75,11 +78,19 @@ from uuid import UUID
|
||||
try:
|
||||
from Cryptodome.Cipher import AES, ARC4, PKCS1_v1_5
|
||||
from Cryptodome.PublicKey import RSA
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES, ARC4, PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
|
||||
# Wrap a stream so that output gets flushed immediately
|
||||
# and also make sure that any unicode strings get
|
||||
@@ -1361,7 +1372,7 @@ class PDFDocument(object):
|
||||
return file_key
|
||||
|
||||
|
||||
def process_with_aes(self, key: bytes, encrypt: bool, data: bytes, repetitions: int = 1, iv: bytes = None):
|
||||
def process_with_aes(self, key, encrypt, data, repetitions = 1, iv = None):
|
||||
if iv is None:
|
||||
keylen = len(key)
|
||||
iv = bytes([0x00]*keylen)
|
||||
@@ -1594,13 +1605,16 @@ class PDFDocument(object):
|
||||
def initialize_ebx_ignoble(self, keyb64, docid, param):
|
||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||
key = keyb64.decode('base64')[:16]
|
||||
|
||||
length = int_value(param.get('Length', 0)) / 8
|
||||
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
|
||||
rights = codecs.decode(str_value(param.get('ADEPT_LICENSE')), "base64")
|
||||
rights = zlib.decompress(rights, -15)
|
||||
rights = etree.fromstring(rights)
|
||||
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
||||
bookkey = ''.join(rights.findtext(expr)).decode('base64')
|
||||
bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
bookkey = AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey)
|
||||
bookkey = unpad(bookkey, 16) # PKCS#7
|
||||
if len(bookkey) > 16:
|
||||
bookkey = bookkey[-16:]
|
||||
ebx_V = int_value(param.get('V', 4))
|
||||
|
||||
@@ -33,10 +33,8 @@ from io import BytesIO
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.py3compat import bchr
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
from Crypto.Util.py3compat import bchr
|
||||
|
||||
try:
|
||||
@@ -750,6 +748,26 @@ def addprottable(ion):
|
||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||
|
||||
|
||||
def pkcs7pad(msg, blocklen):
|
||||
paddinglen = blocklen - len(msg) % blocklen
|
||||
padding = bchr(paddinglen) * paddinglen
|
||||
return msg + padding
|
||||
|
||||
|
||||
def pkcs7unpad(msg, blocklen):
|
||||
_assert(len(msg) % blocklen == 0)
|
||||
|
||||
paddinglen = msg[-1]
|
||||
|
||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
||||
|
||||
return msg[:-paddinglen]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||
OBFUSCATION_TABLE = {
|
||||
"V1": (0x00, None),
|
||||
@@ -865,7 +883,7 @@ class DrmIonVoucher(object):
|
||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||
b = aes.decrypt(self.ciphertext)
|
||||
b = unpad(b, 16)
|
||||
b = pkcs7unpad(b, 16)
|
||||
|
||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||
addprottable(self.drmkey)
|
||||
@@ -1071,7 +1089,7 @@ class DrmIon(object):
|
||||
def processpage(self, ct, civ, outpages, decompress, decrypt):
|
||||
if decrypt:
|
||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||
msg = unpad(aes.decrypt(ct), 16)
|
||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
||||
else:
|
||||
msg = ct
|
||||
|
||||
|
||||
@@ -69,7 +69,11 @@ import getopt
|
||||
import re
|
||||
import traceback
|
||||
import time
|
||||
import html.entities
|
||||
try:
|
||||
import html.entities as htmlentitydefs
|
||||
except:
|
||||
import htmlentitydefs
|
||||
|
||||
import json
|
||||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
@@ -188,7 +192,7 @@ def unescape(text):
|
||||
else:
|
||||
# named entity
|
||||
try:
|
||||
text = chr(html.entities.name2codepoint[text[1:-1]])
|
||||
text = chr(htmlentitydefs.name2codepoint[text[1:-1]])
|
||||
except KeyError:
|
||||
pass
|
||||
return text # leave as is
|
||||
@@ -215,8 +219,11 @@ def GetDecryptedBook(infile, kDatabases, androidFiles, serials, pids, starttime
|
||||
else:
|
||||
mb = topazextract.TopazBook(infile)
|
||||
|
||||
bookname = unescape(mb.getBookTitle())
|
||||
print("Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
|
||||
try:
|
||||
bookname = unescape(mb.getBookTitle())
|
||||
print("Decrypting {1} ebook: {0}".format(bookname, mb.getBookType()))
|
||||
except:
|
||||
print("Decrypting {0} ebook.".format(mb.getBookType()))
|
||||
|
||||
# copy list of pids
|
||||
totalpids = list(pids)
|
||||
@@ -268,7 +275,7 @@ def decryptBook(infile, outdir, kDatabaseFiles, androidFiles, serials, pids):
|
||||
orig_fn_root = os.path.splitext(os.path.basename(infile))[0]
|
||||
if (
|
||||
re.match('^B[A-Z0-9]{9}(_EBOK|_EBSP|_sample)?$', orig_fn_root) or
|
||||
re.match('^{0-9A-F-}{36}$', orig_fn_root)
|
||||
re.match('^[0-9A-F-]{36}$', orig_fn_root)
|
||||
): # Kindle for PC / Mac / Android / Fire / iOS
|
||||
clean_title = cleanup_name(book.getBookTitle())
|
||||
outfilename = "{}_{}".format(orig_fn_root, clean_title)
|
||||
|
||||
@@ -453,7 +453,7 @@ class MobiBook:
|
||||
if crypto_type == 0:
|
||||
print("This book is not encrypted.")
|
||||
# we must still check for Print Replica
|
||||
self.print_replica = (self.loadSection(1)[0:4] == '%MOP')
|
||||
self.print_replica = (self.loadSection(1)[0:4] == b'%MOP')
|
||||
self.mobi_data = self.data_file
|
||||
return
|
||||
if crypto_type != 2 and crypto_type != 1:
|
||||
@@ -524,7 +524,7 @@ class MobiBook:
|
||||
# print "record %d, extra_size %d" %(i,extra_size)
|
||||
decoded_data = PC1(found_key, data[0:len(data) - extra_size])
|
||||
if i==1:
|
||||
self.print_replica = (decoded_data[0:4] == '%MOP')
|
||||
self.print_replica = (decoded_data[0:4] == b'%MOP')
|
||||
mobidataList.append(decoded_data)
|
||||
if extra_size > 0:
|
||||
mobidataList.append(data[-extra_size:])
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
from ignoblekeyGenPassHash import generate_key
|
||||
import sys
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
@@ -21,8 +22,13 @@ DETAILED_MESSAGE = \
|
||||
|
||||
def uStrCmp (s1, s2, caseless=False):
|
||||
import unicodedata as ud
|
||||
str1 = s1 if isinstance(s1, str) else str(s1)
|
||||
str2 = s2 if isinstance(s2, str) else str(s2)
|
||||
if sys.version_info[0] == 2:
|
||||
str1 = s1 if isinstance(s1, unicode) else unicode(s1)
|
||||
str2 = s2 if isinstance(s2, unicode) else unicode(s2)
|
||||
else:
|
||||
str1 = s1 if isinstance(s1, str) else str(s1)
|
||||
str2 = s2 if isinstance(s2, str) else str(s2)
|
||||
|
||||
if caseless:
|
||||
return ud.normalize('NFC', str1.lower()) == ud.normalize('NFC', str2.lower())
|
||||
else:
|
||||
|
||||
@@ -77,7 +77,7 @@ def WineGetKeys(scriptpath, extension, wineprefix=""):
|
||||
pyexec = WinePythonCLI(wineprefix)
|
||||
except NoWinePython3Exception:
|
||||
print('{0} v{1}: Unable to find python3 executable in WINEPREFIX="{2}"'.format(PLUGIN_NAME, PLUGIN_VERSION, wineprefix))
|
||||
return []
|
||||
return [], []
|
||||
|
||||
basepath, script = os.path.split(scriptpath)
|
||||
print("{0} v{1}: Running {2} under Wine".format(PLUGIN_NAME, PLUGIN_VERSION, script))
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = '10.0.0'
|
||||
__version__ = '10.0.3'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
#####################################################################
|
||||
@@ -20,7 +20,7 @@ except NameError:
|
||||
PLUGIN_NAME = 'Obok DeDRM'
|
||||
PLUGIN_SAFE_NAME = PLUGIN_NAME.strip().lower().replace(' ', '_')
|
||||
PLUGIN_DESCRIPTION = _('Removes DRM from Kobo kepubs and adds them to the library.')
|
||||
PLUGIN_VERSION_TUPLE = (10, 0, 0)
|
||||
PLUGIN_VERSION_TUPLE = (10, 0, 3)
|
||||
PLUGIN_VERSION = '.'.join([str(x) for x in PLUGIN_VERSION_TUPLE])
|
||||
HELPFILE_NAME = PLUGIN_SAFE_NAME + '_Help.htm'
|
||||
PLUGIN_AUTHORS = 'Anon'
|
||||
|
||||
@@ -62,7 +62,7 @@ class ConfigWidget(QWidget):
|
||||
|
||||
|
||||
def edit_kobo_directory(self):
|
||||
tmpkobodirectory = QFileDialog.getExistingDirectory(self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.ShowDirsOnly)
|
||||
tmpkobodirectory = QFileDialog.getExistingDirectory(self, "Select Kobo directory", self.kobodirectory or "/home", QFileDialog.Option.ShowDirsOnly)
|
||||
|
||||
if tmpkobodirectory != u"" and tmpkobodirectory is not None:
|
||||
self.kobodirectory = tmpkobodirectory
|
||||
|
||||
@@ -409,7 +409,7 @@ class ReadOnlyTableWidgetItem(QTableWidgetItem):
|
||||
def __init__(self, text):
|
||||
if text is None:
|
||||
text = ''
|
||||
QTableWidgetItem.__init__(self, text, QTableWidgetItem.UserType)
|
||||
QTableWidgetItem.__init__(self, text, QTableWidgetItem.ItemType.UserType)
|
||||
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
|
||||
|
||||
class AuthorTableWidgetItem(ReadOnlyTableWidgetItem):
|
||||
@@ -448,7 +448,7 @@ class IconWidgetItem(ReadOnlyTableWidgetItem):
|
||||
class NumericTableWidgetItem(QTableWidgetItem):
|
||||
|
||||
def __init__(self, number, is_read_only=False):
|
||||
QTableWidgetItem.__init__(self, '', QTableWidgetItem.UserType)
|
||||
QTableWidgetItem.__init__(self, '', QTableWidgetItem.ItemType.UserType)
|
||||
self.setData(Qt.DisplayRole, number)
|
||||
if is_read_only:
|
||||
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Version 10.0.3 July 2022
|
||||
# Fix Calibre 6
|
||||
#
|
||||
# Version 10.0.1 February 2022
|
||||
# Remove OpenSSL support to only support PyCryptodome; clean up the code.
|
||||
#
|
||||
@@ -166,7 +169,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
__version__ = '10.0.1'
|
||||
__about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
|
||||
__about__ = "Obok v{0}\nCopyright © 2012-2022 Physisticated et al.".format(__version__)
|
||||
|
||||
import sys
|
||||
import os
|
||||
@@ -185,10 +188,17 @@ import tempfile
|
||||
|
||||
try:
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Util.Padding import unpad
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import unpad
|
||||
|
||||
def unpad(data, padding=16):
|
||||
if sys.version_info[0] == 2:
|
||||
pad_len = ord(data[-1])
|
||||
else:
|
||||
pad_len = data[-1]
|
||||
|
||||
return data[:-pad_len]
|
||||
|
||||
|
||||
can_parse_xml = True
|
||||
try:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<body>
|
||||
|
||||
<h1>Obok DeDRM Plugin</h1>
|
||||
<h3>(version 10.0.0)</h3>
|
||||
<h3>(version 10.0.2)</h3>
|
||||
|
||||
<h3>Installation:</h3>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Welcome to the tools!
|
||||
=====================
|
||||
|
||||
This file is to give users a quick overview of what is available and how to get started. This document is part of the DeDRM Tools archive from Apprentice Harper's github repository: https://github.com/apprenticeharper/DeDRM_tools/
|
||||
This file is to give users a quick overview of what is available and how to get started. This document is part of the DeDRM Tools archive from noDRM's github repository: https://github.com/noDRM/DeDRM_tools/
|
||||
|
||||
This archive includes calibre plugins to remove DRM from:
|
||||
|
||||
@@ -12,21 +12,22 @@ This archive includes calibre plugins to remove DRM from:
|
||||
|
||||
These tools do NOT work with Apple's iBooks FairPlay DRM. Use iBook Copy from TunesKit.
|
||||
These tools no longer work well with books from Barnes & Noble.
|
||||
Due to a DMCA request, these tools no longer work with LCP-encrypted books - see https://github.com/noDRM/DeDRM_tools/issues/18 for details.
|
||||
|
||||
For limitations and work-arounds, see the FAQ at https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md
|
||||
For limitations and work-arounds, see the FAQ at https://github.com/noDRM/DeDRM_tools/blob/master/FAQs.md
|
||||
|
||||
About the tools
|
||||
---------------
|
||||
These tools are updated and maintained by Apprentice Harper and many others. You can find the latest updates at Apprentice Harper's github repository https://github.com/apprenticeharper/DeDRM_tools/ and get support by creating an issue at the repository (github account required) or by posting a comment at Apprentice Alf's blog: http://www.apprenticealf.wordpress.com/
|
||||
These tools are updated and maintained by noDRM and many others. They are based on Apprentice Harper's Calibre plugin. You can find the latest updates at noDRM's github repository https://github.com/noDRM/DeDRM_tools/ and get support by creating an issue at the repository (github account required).
|
||||
|
||||
If you re-post these tools, a link to the repository and/or the blog would be appreciated.
|
||||
If you re-post these tools, a link to the repository would be appreciated.
|
||||
|
||||
The tools are provided in the form of plugins for calibre. Calibre is an open source freeware ebook library manager. It is the best tool around for keeping track of your ebooks.
|
||||
|
||||
|
||||
DeDRM plugin for calibre (Mac OS X, Windows)
|
||||
DeDRM plugin for calibre (Linux, Mac OS X and Windows)
|
||||
-------------------------------------------------------
|
||||
calibe 5.x and later are now written in Python 3, and plugins must also use Python 3. If you have calibre 5, you must use version 7.x or later of the plugins. For calibre 4.x and earlier, use version 6.8.x of the plugins.
|
||||
calibe 5.x and later are now written in Python 3, and plugins must also use Python 3.
|
||||
|
||||
The DeDRM plugin for calibre removes DRM from your Kindle and Adobe DRM ebooks when they are imported to calibre. Just install the DeDRM plugin (DeDRM_plugin.zip), following the instructions and configuration directions provided in the ReadMe file and the help links in the plugin's configuration dialogs.
|
||||
|
||||
@@ -40,18 +41,6 @@ To import ebooks from the Kobo Desktop app or from a Kobo ebook reader, install
|
||||
For instructions, see the obok_plugin_ReadMe.txt file.
|
||||
|
||||
|
||||
DeDRM application for Mac OS X users: (Mac OS X 10.6 and above)
|
||||
---------------------------------------------------------------
|
||||
DeDRM application for Windows users: (Windows XP through Windows 10)
|
||||
------------------------------------------------------------------
|
||||
As of Version 6.7 of the tools, these are no longer provided or supported.
|
||||
|
||||
|
||||
Linux support
|
||||
-------------
|
||||
It may be possible to use the plugins on a Linux system, but no support is given at this time.
|
||||
|
||||
|
||||
Credits
|
||||
-------
|
||||
The original inept and ignoble scripts were by i♥cabbages
|
||||
@@ -61,7 +50,8 @@ The original topaz DRM removal script was by CMBDTC
|
||||
The original topaz format conversion scripts were by some_updates, clarknova and Bart Simpson
|
||||
The original KFX format decryption was by lulzkabulz, converted to python by Apprentice Naomi and integrated into the tools by tomthumb1997
|
||||
The alfcrypto library is by some_updates
|
||||
The DeDRM plugin was based on plugins by DiapDealer and is maintained by Apprentice Alf and Apprentice Harper
|
||||
The DeDRM plugin is based on plugins by DiapDealer and is currently maintained by noDRM
|
||||
The DeDRM plugin has been maintained by Apprentice Alf and Apprentice Harper until 2021.
|
||||
|
||||
The original obok script was by Physisticated
|
||||
The plugin conversion was done anonymously.
|
||||
|
||||
Reference in New Issue
Block a user