From 9c41716e5ef3812807859fc91497fe158796fb3f Mon Sep 17 00:00:00 2001 From: NoDRM Date: Tue, 16 Nov 2021 11:48:53 +0100 Subject: [PATCH] Add B&N PDF DeDRM (untested), match UUID for Adobe PDFs --- DeDRM_plugin/__init__.py | 32 +++++++++++++++++- DeDRM_plugin/ignoblepdf.py | 7 ++-- DeDRM_plugin/ineptpdf.py | 68 +++++++++++++++++++++++++++++++++----- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/DeDRM_plugin/__init__.py b/DeDRM_plugin/__init__.py index 9ee3a39..6fc4594 100644 --- a/DeDRM_plugin/__init__.py +++ b/DeDRM_plugin/__init__.py @@ -544,7 +544,7 @@ class DeDRM(FileTypePlugin): # If we end up here, we didn't find a key with a matching UUID, so lets just try all of them. - # Attempt to decrypt epub with each encryption key (generated or provided). + # Attempt to decrypt PDF with each encryption key (generated or provided). for keyname, userkeyhex in dedrmprefs['adeptkeys'].items(): userkey = codecs.decode(userkeyhex,'hex') print("{0} v{1}: Trying encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname)) @@ -640,6 +640,36 @@ class DeDRM(FileTypePlugin): except Exception as e: pass + + # Unable to decrypt the PDF with any of the existing keys. Is it a B&N PDF? + # Attempt to decrypt PDF with each encryption key (generated or provided). + for keyname, userkey in dedrmprefs['bandnkeys'].items(): + keyname_masked = "".join(("X" if (x.isdigit()) else x) for x in keyname) + print("{0} v{1}: Trying Encryption key {2:s}".format(PLUGIN_NAME, PLUGIN_VERSION, keyname_masked)) + of = self.temporary_file(".pdf") + + # Give the user key, ebook and TemporaryPersistent file to the decryption function. + try: + result = ineptpdf.decryptBook(userkey, path_to_ebook, of.name, False) + except ineptpdf.ADEPTNewVersionError: + print("{0} v{1}: Book uses unsupported (too new) Adobe DRM.".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) + return path_to_ebook + except: + print("{0} v{1}: Exception when decrypting after {2:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) + traceback.print_exc() + result = 1 + + of.close() + + if result == 0: + # Decryption was successful. + # Return the modified PersistentTemporary file to calibre. + print("{0} v{1}: Decrypted with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)) + return of.name + + print("{0} v{1}: Failed to decrypt with key {2:s} after {3:.1f} seconds".format(PLUGIN_NAME, PLUGIN_VERSION,keyname,time.time()-self.starttime)) + + # Something went wrong with decryption. print("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION,time.time()-self.starttime)) raise DeDRMError("{0} v{1}: Ultimately failed to decrypt after {2:.1f} seconds. Read the FAQs at Harper's repository: https://github.com/apprenticeharper/DeDRM_tools/blob/master/FAQs.md".format(PLUGIN_NAME, PLUGIN_VERSION, time.time()-self.starttime)) diff --git a/DeDRM_plugin/ignoblepdf.py b/DeDRM_plugin/ignoblepdf.py index 9f2c4db..1e6d66a 100644 --- a/DeDRM_plugin/ignoblepdf.py +++ b/DeDRM_plugin/ignoblepdf.py @@ -24,6 +24,7 @@ Decrypts Barnes & Noble encrypted PDF files. __license__ = 'GPL v3' __version__ = "0.3" +import codecs import sys import os import re @@ -312,7 +313,7 @@ class PSLiteral(PSObject): Use PSLiteralTable.intern() instead. ''' def __init__(self, name): - self.name = name + self.name = name.decode('utf-8') return def __repr__(self): @@ -1448,7 +1449,7 @@ class PDFDocument(object): self.decipher = self.decipher_rc4 # XXX may be AES # aes elif V == 4 and length == 128: - elf.decipher = self.decipher_aes + self.decipher = self.decipher_aes elif V == 4 and length == 256: raise PDFNotImplementedError('AES256 encryption is currently unsupported') self.ready = True @@ -1491,7 +1492,7 @@ class PDFDocument(object): # proper length unknown try with whatever you have print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)) print("length is %d and len(bookkey) is %d" % (length, len(bookkey))) - print("bookkey[0] is %d" % ord(bookkey[0])) + print("bookkey[0] is %d" % bookkey[0]) if ebx_V == 3: V = 3 else: diff --git a/DeDRM_plugin/ineptpdf.py b/DeDRM_plugin/ineptpdf.py index f8a5592..51ca17e 100755 --- a/DeDRM_plugin/ineptpdf.py +++ b/DeDRM_plugin/ineptpdf.py @@ -1488,7 +1488,7 @@ class PDFDocument(object): # Perform the initialization with a given password. # This step is mandatory even if there's no password associated # with the document. - def initialize(self, password=b''): + def initialize(self, password=b'', inept=True): if not self.encryption: self.is_printable = self.is_modifiable = self.is_extractable = True self.ready = True @@ -1500,8 +1500,11 @@ class PDFDocument(object): return self.initialize_adobe_ps(password, docid, param) if type == 'Standard': return self.initialize_standard(password, docid, param) - if type == 'EBX_HANDLER': - return self.initialize_ebx(password, docid, param) + if type == 'EBX_HANDLER' and inept is True: + return self.initialize_ebx_inept(password, docid, param) + if type == 'EBX_HANDLER' and inept is False: + return self.initialize_ebx_ignoble(password, docid, param) + raise PDFEncryptionError('Unknown filter: param=%r' % param) def initialize_adobe_ps(self, password, docid, param): @@ -1642,7 +1645,52 @@ class PDFDocument(object): return True - def initialize_ebx(self, password, docid, param): + def initialize_ebx_ignoble(self, keyb64, docid, param): + self.is_printable = self.is_modifiable = self.is_extractable = True + key = keyb64.decode('base64')[:16] + aes = AES.new(key,AES.MODE_CBC,"\x00" * len(key)) + length = int_value(param.get('Length', 0)) / 8 + rights = str_value(param.get('ADEPT_LICENSE')).decode('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 = aes.decrypt(bookkey) + bookkey = bookkey[:-ord(bookkey[-1])] + bookkey = bookkey[-16:] + ebx_V = int_value(param.get('V', 4)) + ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6)) + # added because of improper booktype / decryption book session key errors + if length > 0: + if len(bookkey) == length: + if ebx_V == 3: + V = 3 + else: + V = 2 + elif len(bookkey) == length + 1: + V = bookkey[0] + bookkey = bookkey[1:] + else: + print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)) + print("length is %d and len(bookkey) is %d" % (length, len(bookkey))) + print("bookkey[0] is %d" % bookkey[0]) + raise ADEPTError('error decrypting book session key - mismatched length') + else: + # proper length unknown try with whatever you have + print("ebx_V is %d and ebx_type is %d" % (ebx_V, ebx_type)) + print("length is %d and len(bookkey) is %d" % (length, len(bookkey))) + print("bookkey[0] is %d" % ord(bookkey[0])) + if ebx_V == 3: + V = 3 + else: + V = 2 + self.decrypt_key = bookkey + self.genkey = self.genkey_v3 if V == 3 else self.genkey_v2 + self.decipher = self.decrypt_rc4 + self.ready = True + return + + def initialize_ebx_inept(self, password, docid, param): self.is_printable = self.is_modifiable = self.is_extractable = True rsa = RSA(password) length = int_value(param.get('Length', 0)) // 8 @@ -2042,15 +2090,19 @@ class PDFObjStrmParser(PDFParser): def adeptGetUserUUID(inf): try: doc = PDFDocument() + inf = open(inf, 'rb') pars = PDFParser(doc, inf) (docid, param) = doc.encryption type = literal_name(param['Filter']) if type != 'EBX_HANDLER': # No EBX_HANDLER, no idea which user key can decrypt this. + inf.close() return None rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64') + inf.close() + rights = zlib.decompress(rights, -15) rights = etree.fromstring(rights) expr = './/{http://ns.adobe.com/adept}user' @@ -2066,14 +2118,14 @@ def adeptGetUserUUID(inf): ### My own code, for which there is none else to blame class PDFSerializer(object): - def __init__(self, inf, userkey): + def __init__(self, inf, userkey, inept=True): global GEN_XREF_STM, gen_xref_stm gen_xref_stm = GEN_XREF_STM > 1 self.version = inf.read(8) inf.seek(0) self.doc = doc = PDFDocument() parser = PDFParser(doc, inf) - doc.initialize(userkey) + doc.initialize(userkey, inept) self.objids = objids = set() for xref in reversed(doc.xrefs): trailer = xref.trailer @@ -2263,11 +2315,11 @@ class PDFSerializer(object): -def decryptBook(userkey, inpath, outpath): +def decryptBook(userkey, inpath, outpath, inept=True): if RSA is None: raise ADEPTError("PyCryptodome or OpenSSL must be installed.") with open(inpath, 'rb') as inf: - serializer = PDFSerializer(inf, userkey) + serializer = PDFSerializer(inf, userkey, inept) with open(outpath, 'wb') as outf: # help construct to make sure the method runs to the end try: