mirror of
https://gitea.com/bipinkrish/DeGourou.git
synced 2024-11-17 06:40:22 +00:00
Installable from Pip and use in CMD
This commit is contained in:
parent
4e61897e17
commit
eb7d65bab5
18 changed files with 520 additions and 470 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
- name: Build Executable
|
||||
uses: Nuitka/Nuitka-Action@main
|
||||
with:
|
||||
script-name: DeGourou.py
|
||||
script-name: DeGourou/DeGourou.py
|
||||
onefile: true
|
||||
standalone: true
|
||||
|
||||
|
|
29
DeGourou.py → DeGourou/DeGourou.py
Executable file → Normal file
29
DeGourou.py → DeGourou/DeGourou.py
Executable file → Normal file
|
@ -1,18 +1,18 @@
|
|||
#!/usr/bin/env python
|
||||
from setup.loginAccount import loginAndGetKey
|
||||
from setup.fulfill import downloadFile
|
||||
from .setup.loginAccount import loginAndGetKey
|
||||
from .setup.fulfill import downloadFile
|
||||
|
||||
from decrypt.decodePDF import decryptPDF
|
||||
from decrypt.decodeEPUB import decryptEPUB
|
||||
from .decrypt.decodePDF import decryptPDF
|
||||
from .decrypt.decodeEPUB import decryptEPUB
|
||||
|
||||
import argparse
|
||||
from os import mkdir, remove, rename
|
||||
from os.path import exists
|
||||
|
||||
from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from decrypt.params import KEYPATH
|
||||
from setup.data import createDefaultFiles
|
||||
from setup.ia import SESSION_FILE, manage_login, get_book, return_book
|
||||
from .setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from .decrypt.params import KEYPATH
|
||||
from .setup.data import createDefaultFiles
|
||||
from .setup.ia import SESSION_FILE, manage_login, get_book, return_book
|
||||
|
||||
|
||||
def loginADE(email, password):
|
||||
|
@ -32,7 +32,7 @@ def loginIA(email,password):
|
|||
manage_login(email,password)
|
||||
print()
|
||||
|
||||
def main(acsmFile, outputFilename):
|
||||
def start(acsmFile, outputFilename):
|
||||
if not exists('account'): mkdir('account')
|
||||
|
||||
# setting up the account and keys
|
||||
|
@ -81,13 +81,13 @@ def handle_IA(url,format):
|
|||
if acsmFile is None:
|
||||
print("Could not get Book, try using ACSm file as input")
|
||||
return
|
||||
main(acsmFile,None)
|
||||
start(acsmFile,None)
|
||||
remove(acsmFile)
|
||||
if(return_book(url) is None):
|
||||
print("Please return it yourself")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Download and Decrypt an encrypted PDF or EPUB file.")
|
||||
parser.add_argument("-f", type=str, nargs='?', default=None, help="path to the ACSM file")
|
||||
parser.add_argument("-u", type=str, nargs='?', default=None, help="book url from InternetArchive")
|
||||
|
@ -134,8 +134,11 @@ if __name__ == "__main__":
|
|||
elif args.f == None:
|
||||
if exists("URLLink.acsm"):
|
||||
args.f = "URLLink.acsm"
|
||||
main(args.f, args.o)
|
||||
start(args.f, args.o)
|
||||
else: parser.print_help()
|
||||
|
||||
else:
|
||||
main(args.f, args.o)
|
||||
start(args.f, args.o)
|
||||
|
||||
|
||||
if __name__ == "__main__": main()
|
|
@ -1,313 +1,313 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ineptepub.py
|
||||
# Copyright © 2009-2022 by i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
from decrypt.params import KEYPATH
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import base64
|
||||
import zlib
|
||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
from decrypt.zeroedzipinfo import ZeroedZipInfo
|
||||
from contextlib import closing
|
||||
from lxml import etree
|
||||
from uuid import UUID
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
||||
from Cryptodome.PublicKey import RSA
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
|
||||
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]
|
||||
|
||||
|
||||
class ADEPTError(Exception):
|
||||
pass
|
||||
|
||||
class ADEPTNewVersionError(Exception):
|
||||
pass
|
||||
|
||||
META_NAMES = ('mimetype', 'META-INF/rights.xml')
|
||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||
|
||||
class Decryptor(object):
|
||||
def __init__(self, bookkey, encryption):
|
||||
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||
self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
|
||||
self._encryption = etree.fromstring(encryption)
|
||||
self._encrypted = encrypted = set()
|
||||
self._encryptedForceNoDecomp = encryptedForceNoDecomp = set()
|
||||
self._otherData = otherData = set()
|
||||
|
||||
self._json_elements_to_remove = json_elements_to_remove = set()
|
||||
self._has_remaining_xml = False
|
||||
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
|
||||
enc('CipherReference'))
|
||||
for elem in self._encryption.findall(expr):
|
||||
path = elem.get('URI', None)
|
||||
encryption_type_url = (elem.getparent().getparent().find("./%s" % (enc('EncryptionMethod'))).get('Algorithm', None))
|
||||
if path is not None:
|
||||
if (encryption_type_url == "http://www.w3.org/2001/04/xmlenc#aes128-cbc"):
|
||||
# Adobe
|
||||
path = path.encode('utf-8')
|
||||
encrypted.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
elif (encryption_type_url == "http://ns.adobe.com/adept/xmlenc#aes128-cbc-uncompressed"):
|
||||
# Adobe uncompressed, for stuff like video files
|
||||
path = path.encode('utf-8')
|
||||
encryptedForceNoDecomp.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
else:
|
||||
path = path.encode('utf-8')
|
||||
otherData.add(path)
|
||||
self._has_remaining_xml = True
|
||||
|
||||
for elem in json_elements_to_remove:
|
||||
elem.getparent().remove(elem)
|
||||
|
||||
def check_if_remaining(self):
|
||||
return self._has_remaining_xml
|
||||
|
||||
def get_xml(self):
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + etree.tostring(self._encryption, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
|
||||
|
||||
def decompress(self, bytes):
|
||||
dc = zlib.decompressobj(-15)
|
||||
try:
|
||||
decompressed_bytes = dc.decompress(bytes)
|
||||
ex = dc.decompress(b'Z') + dc.flush()
|
||||
if ex:
|
||||
decompressed_bytes = decompressed_bytes + ex
|
||||
except:
|
||||
# possibly not compressed by zip - just return bytes
|
||||
return bytes
|
||||
return decompressed_bytes
|
||||
|
||||
def decrypt(self, path, data):
|
||||
if path.encode('utf-8') in self._encrypted or path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self._aes.decrypt(data)[16:]
|
||||
if type(data[-1]) != int:
|
||||
place = ord(data[-1])
|
||||
else:
|
||||
place = data[-1]
|
||||
data = data[:-place]
|
||||
if not path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self.decompress(data)
|
||||
return data
|
||||
|
||||
# check file to make check whether it's probably an Adobe Adept encrypted ePub
|
||||
def adeptBook(inpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
return False
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) in [192, 172, 64]:
|
||||
return True
|
||||
except:
|
||||
# if we couldn't check, assume it is
|
||||
return True
|
||||
return False
|
||||
|
||||
def isPassHashBook(inpath):
|
||||
# If this is an Adobe book, check if it's a PassHash-encrypted book (B&N)
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
return False
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) == 64:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# Checks the license file and returns the UUID the book is licensed for.
|
||||
# This is used so that the Calibre plugin can pick the correct decryption key
|
||||
# first try without having to loop through all possible keys.
|
||||
def adeptGetUserUUID(inpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('user'),)
|
||||
user_uuid = ''.join(rights.findtext(expr))
|
||||
if user_uuid[:9] != "urn:uuid:":
|
||||
return None
|
||||
return user_uuid[9:]
|
||||
except:
|
||||
return None
|
||||
|
||||
def removeHardening(rights, keytype, keydata):
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
textGetter = lambda name: ''.join(rights.findtext('.//%s' % (adept(name),)))
|
||||
|
||||
# Gather what we need, and generate the IV
|
||||
resourceuuid = UUID(textGetter("resource"))
|
||||
deviceuuid = UUID(textGetter("device"))
|
||||
fullfillmentuuid = UUID(textGetter("fulfillment")[:36])
|
||||
kekiv = UUID(int=resourceuuid.int ^ deviceuuid.int ^ fullfillmentuuid.int).bytes
|
||||
|
||||
# Derive kek from just "keytype"
|
||||
rem = int(keytype, 10) % 16
|
||||
H = hashlib.sha256(keytype.encode("ascii")).digest()
|
||||
kek = H[2*rem : 16 + rem] + H[rem : 2*rem]
|
||||
|
||||
return unpad(AES.new(kek, AES.MODE_CBC, kekiv).decrypt(keydata), 16) # PKCS#7
|
||||
|
||||
def decryptBook(userkey, inpath, outpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = inf.namelist()
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
for name in META_NAMES:
|
||||
namelist.remove(name)
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkeyelem = rights.find(expr)
|
||||
bookkey = bookkeyelem.text
|
||||
keytype = bookkeyelem.attrib.get('keyType', '0')
|
||||
if len(bookkey) >= 172 and int(keytype, 10) > 2:
|
||||
print("{0:s} is a secure Adobe Adept ePub with hardening.".format(os.path.basename(inpath)))
|
||||
elif len(bookkey) == 172:
|
||||
print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||
elif len(bookkey) == 64:
|
||||
print("{0:s} is a secure Adobe PassHash (B&N) ePub.".format(os.path.basename(inpath)))
|
||||
else:
|
||||
print("{0:s} is not an Adobe-protected ePub!".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
|
||||
if len(bookkey) != 64:
|
||||
# Normal or "hardened" Adobe ADEPT
|
||||
rsakey = RSA.importKey(userkey) # parses the ASN1 structure
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
if int(keytype, 10) > 2:
|
||||
bookkey = removeHardening(rights, keytype, bookkey)
|
||||
try:
|
||||
bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
|
||||
except ValueError:
|
||||
bookkey = None
|
||||
|
||||
if bookkey is None:
|
||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||
return 2
|
||||
else:
|
||||
# Adobe PassHash / B&N
|
||||
key = base64.b64decode(userkey)[:16]
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
|
||||
|
||||
if len(bookkey) > 16:
|
||||
bookkey = bookkey[-16:]
|
||||
|
||||
encryption = inf.read('META-INF/encryption.xml')
|
||||
decryptor = Decryptor(bookkey, encryption)
|
||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||
|
||||
for path in (["mimetype"] + namelist):
|
||||
data = inf.read(path)
|
||||
zi = ZipInfo(path)
|
||||
zi.compress_type=ZIP_DEFLATED
|
||||
|
||||
if path == "mimetype":
|
||||
zi.compress_type = ZIP_STORED
|
||||
|
||||
elif path == "META-INF/encryption.xml":
|
||||
# Check if there's still something in there
|
||||
if (decryptor.check_if_remaining()):
|
||||
data = decryptor.get_xml()
|
||||
print("Adding encryption.xml for the remaining embedded files.")
|
||||
# We removed DRM, but there's still stuff like obfuscated fonts.
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
# get the file info, including time-stamp
|
||||
oldzi = inf.getinfo(path)
|
||||
# copy across useful fields
|
||||
zi.date_time = oldzi.date_time
|
||||
zi.comment = oldzi.comment
|
||||
zi.extra = oldzi.extra
|
||||
zi.internal_attr = oldzi.internal_attr
|
||||
# external attributes are dependent on the create system, so copy both.
|
||||
zi.external_attr = oldzi.external_attr
|
||||
|
||||
zi.volume = oldzi.volume
|
||||
zi.create_system = oldzi.create_system
|
||||
zi.create_version = oldzi.create_version
|
||||
|
||||
if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
|
||||
# If the file name or the comment contains any non-ASCII char, set the UTF8-flag
|
||||
zi.flag_bits |= 0x800
|
||||
except:
|
||||
pass
|
||||
|
||||
# Python 3 has a bug where the external_attr is reset to `0o600 << 16`
|
||||
# if it's NULL, so we need a workaround:
|
||||
if zi.external_attr == 0:
|
||||
zi = ZeroedZipInfo(zi)
|
||||
|
||||
|
||||
if path == "META-INF/encryption.xml":
|
||||
outf.writestr(zi, data)
|
||||
else:
|
||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||
except:
|
||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def decryptEPUB(inpath):
|
||||
keypath = KEYPATH
|
||||
outpath = os.path.basename(inpath).removesuffix(".epub") + "_decrypted.epub"
|
||||
userkey = open(keypath,'rb').read()
|
||||
result = decryptBook(userkey, inpath, outpath)
|
||||
if result == 0:
|
||||
print("Successfully decrypted")
|
||||
return outpath
|
||||
else:
|
||||
print("Decryption failed")
|
||||
return None
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ineptepub.py
|
||||
# Copyright © 2009-2022 by i♥cabbages, Apprentice Harper et al.
|
||||
|
||||
# Released under the terms of the GNU General Public Licence, version 3
|
||||
# <http://www.gnu.org/licenses/>
|
||||
|
||||
"""
|
||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||
"""
|
||||
|
||||
from .params import KEYPATH
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "8.0"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import base64
|
||||
import zlib
|
||||
from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
||||
from .zeroedzipinfo import ZeroedZipInfo
|
||||
from contextlib import closing
|
||||
from lxml import etree
|
||||
from uuid import UUID
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
||||
from Cryptodome.PublicKey import RSA
|
||||
except ImportError:
|
||||
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
|
||||
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]
|
||||
|
||||
|
||||
class ADEPTError(Exception):
|
||||
pass
|
||||
|
||||
class ADEPTNewVersionError(Exception):
|
||||
pass
|
||||
|
||||
META_NAMES = ('mimetype', 'META-INF/rights.xml')
|
||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||
|
||||
class Decryptor(object):
|
||||
def __init__(self, bookkey, encryption):
|
||||
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||
self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
|
||||
self._encryption = etree.fromstring(encryption)
|
||||
self._encrypted = encrypted = set()
|
||||
self._encryptedForceNoDecomp = encryptedForceNoDecomp = set()
|
||||
self._otherData = otherData = set()
|
||||
|
||||
self._json_elements_to_remove = json_elements_to_remove = set()
|
||||
self._has_remaining_xml = False
|
||||
expr = './%s/%s/%s' % (enc('EncryptedData'), enc('CipherData'),
|
||||
enc('CipherReference'))
|
||||
for elem in self._encryption.findall(expr):
|
||||
path = elem.get('URI', None)
|
||||
encryption_type_url = (elem.getparent().getparent().find("./%s" % (enc('EncryptionMethod'))).get('Algorithm', None))
|
||||
if path is not None:
|
||||
if (encryption_type_url == "http://www.w3.org/2001/04/xmlenc#aes128-cbc"):
|
||||
# Adobe
|
||||
path = path.encode('utf-8')
|
||||
encrypted.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
elif (encryption_type_url == "http://ns.adobe.com/adept/xmlenc#aes128-cbc-uncompressed"):
|
||||
# Adobe uncompressed, for stuff like video files
|
||||
path = path.encode('utf-8')
|
||||
encryptedForceNoDecomp.add(path)
|
||||
json_elements_to_remove.add(elem.getparent().getparent())
|
||||
else:
|
||||
path = path.encode('utf-8')
|
||||
otherData.add(path)
|
||||
self._has_remaining_xml = True
|
||||
|
||||
for elem in json_elements_to_remove:
|
||||
elem.getparent().remove(elem)
|
||||
|
||||
def check_if_remaining(self):
|
||||
return self._has_remaining_xml
|
||||
|
||||
def get_xml(self):
|
||||
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + etree.tostring(self._encryption, encoding="utf-8", pretty_print=True, xml_declaration=False).decode("utf-8")
|
||||
|
||||
|
||||
def decompress(self, bytes):
|
||||
dc = zlib.decompressobj(-15)
|
||||
try:
|
||||
decompressed_bytes = dc.decompress(bytes)
|
||||
ex = dc.decompress(b'Z') + dc.flush()
|
||||
if ex:
|
||||
decompressed_bytes = decompressed_bytes + ex
|
||||
except:
|
||||
# possibly not compressed by zip - just return bytes
|
||||
return bytes
|
||||
return decompressed_bytes
|
||||
|
||||
def decrypt(self, path, data):
|
||||
if path.encode('utf-8') in self._encrypted or path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self._aes.decrypt(data)[16:]
|
||||
if type(data[-1]) != int:
|
||||
place = ord(data[-1])
|
||||
else:
|
||||
place = data[-1]
|
||||
data = data[:-place]
|
||||
if not path.encode('utf-8') in self._encryptedForceNoDecomp:
|
||||
data = self.decompress(data)
|
||||
return data
|
||||
|
||||
# check file to make check whether it's probably an Adobe Adept encrypted ePub
|
||||
def adeptBook(inpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
return False
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) in [192, 172, 64]:
|
||||
return True
|
||||
except:
|
||||
# if we couldn't check, assume it is
|
||||
return True
|
||||
return False
|
||||
|
||||
def isPassHashBook(inpath):
|
||||
# If this is an Adobe book, check if it's a PassHash-encrypted book (B&N)
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = set(inf.namelist())
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
return False
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkey = ''.join(rights.findtext(expr))
|
||||
if len(bookkey) == 64:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# Checks the license file and returns the UUID the book is licensed for.
|
||||
# This is used so that the Calibre plugin can pick the correct decryption key
|
||||
# first try without having to loop through all possible keys.
|
||||
def adeptGetUserUUID(inpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('user'),)
|
||||
user_uuid = ''.join(rights.findtext(expr))
|
||||
if user_uuid[:9] != "urn:uuid:":
|
||||
return None
|
||||
return user_uuid[9:]
|
||||
except:
|
||||
return None
|
||||
|
||||
def removeHardening(rights, keytype, keydata):
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
textGetter = lambda name: ''.join(rights.findtext('.//%s' % (adept(name),)))
|
||||
|
||||
# Gather what we need, and generate the IV
|
||||
resourceuuid = UUID(textGetter("resource"))
|
||||
deviceuuid = UUID(textGetter("device"))
|
||||
fullfillmentuuid = UUID(textGetter("fulfillment")[:36])
|
||||
kekiv = UUID(int=resourceuuid.int ^ deviceuuid.int ^ fullfillmentuuid.int).bytes
|
||||
|
||||
# Derive kek from just "keytype"
|
||||
rem = int(keytype, 10) % 16
|
||||
H = hashlib.sha256(keytype.encode("ascii")).digest()
|
||||
kek = H[2*rem : 16 + rem] + H[rem : 2*rem]
|
||||
|
||||
return unpad(AES.new(kek, AES.MODE_CBC, kekiv).decrypt(keydata), 16) # PKCS#7
|
||||
|
||||
def decryptBook(userkey, inpath, outpath):
|
||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||
namelist = inf.namelist()
|
||||
if 'META-INF/rights.xml' not in namelist or \
|
||||
'META-INF/encryption.xml' not in namelist:
|
||||
print("{0:s} is DRM-free.".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
for name in META_NAMES:
|
||||
namelist.remove(name)
|
||||
try:
|
||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||
expr = './/%s' % (adept('encryptedKey'),)
|
||||
bookkeyelem = rights.find(expr)
|
||||
bookkey = bookkeyelem.text
|
||||
keytype = bookkeyelem.attrib.get('keyType', '0')
|
||||
if len(bookkey) >= 172 and int(keytype, 10) > 2:
|
||||
print("{0:s} is a secure Adobe Adept ePub with hardening.".format(os.path.basename(inpath)))
|
||||
elif len(bookkey) == 172:
|
||||
print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||
elif len(bookkey) == 64:
|
||||
print("{0:s} is a secure Adobe PassHash (B&N) ePub.".format(os.path.basename(inpath)))
|
||||
else:
|
||||
print("{0:s} is not an Adobe-protected ePub!".format(os.path.basename(inpath)))
|
||||
return 1
|
||||
|
||||
if len(bookkey) != 64:
|
||||
# Normal or "hardened" Adobe ADEPT
|
||||
rsakey = RSA.importKey(userkey) # parses the ASN1 structure
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
if int(keytype, 10) > 2:
|
||||
bookkey = removeHardening(rights, keytype, bookkey)
|
||||
try:
|
||||
bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
|
||||
except ValueError:
|
||||
bookkey = None
|
||||
|
||||
if bookkey is None:
|
||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||
return 2
|
||||
else:
|
||||
# Adobe PassHash / B&N
|
||||
key = base64.b64decode(userkey)[:16]
|
||||
bookkey = base64.b64decode(bookkey)
|
||||
bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
|
||||
|
||||
if len(bookkey) > 16:
|
||||
bookkey = bookkey[-16:]
|
||||
|
||||
encryption = inf.read('META-INF/encryption.xml')
|
||||
decryptor = Decryptor(bookkey, encryption)
|
||||
kwds = dict(compression=ZIP_DEFLATED, allowZip64=False)
|
||||
with closing(ZipFile(open(outpath, 'wb'), 'w', **kwds)) as outf:
|
||||
|
||||
for path in (["mimetype"] + namelist):
|
||||
data = inf.read(path)
|
||||
zi = ZipInfo(path)
|
||||
zi.compress_type=ZIP_DEFLATED
|
||||
|
||||
if path == "mimetype":
|
||||
zi.compress_type = ZIP_STORED
|
||||
|
||||
elif path == "META-INF/encryption.xml":
|
||||
# Check if there's still something in there
|
||||
if (decryptor.check_if_remaining()):
|
||||
data = decryptor.get_xml()
|
||||
print("Adding encryption.xml for the remaining embedded files.")
|
||||
# We removed DRM, but there's still stuff like obfuscated fonts.
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
# get the file info, including time-stamp
|
||||
oldzi = inf.getinfo(path)
|
||||
# copy across useful fields
|
||||
zi.date_time = oldzi.date_time
|
||||
zi.comment = oldzi.comment
|
||||
zi.extra = oldzi.extra
|
||||
zi.internal_attr = oldzi.internal_attr
|
||||
# external attributes are dependent on the create system, so copy both.
|
||||
zi.external_attr = oldzi.external_attr
|
||||
|
||||
zi.volume = oldzi.volume
|
||||
zi.create_system = oldzi.create_system
|
||||
zi.create_version = oldzi.create_version
|
||||
|
||||
if any(ord(c) >= 128 for c in path) or any(ord(c) >= 128 for c in zi.comment):
|
||||
# If the file name or the comment contains any non-ASCII char, set the UTF8-flag
|
||||
zi.flag_bits |= 0x800
|
||||
except:
|
||||
pass
|
||||
|
||||
# Python 3 has a bug where the external_attr is reset to `0o600 << 16`
|
||||
# if it's NULL, so we need a workaround:
|
||||
if zi.external_attr == 0:
|
||||
zi = ZeroedZipInfo(zi)
|
||||
|
||||
|
||||
if path == "META-INF/encryption.xml":
|
||||
outf.writestr(zi, data)
|
||||
else:
|
||||
outf.writestr(zi, decryptor.decrypt(path, data))
|
||||
except:
|
||||
print("Could not decrypt {0:s} because of an exception:\n{1:s}".format(os.path.basename(inpath), traceback.format_exc()))
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def decryptEPUB(inpath):
|
||||
keypath = KEYPATH
|
||||
outpath = os.path.basename(inpath).removesuffix(".epub") + "_decrypted.epub"
|
||||
userkey = open(keypath,'rb').read()
|
||||
result = decryptBook(userkey, inpath, outpath)
|
||||
if result == 0:
|
||||
print("Successfully decrypted")
|
||||
return outpath
|
||||
else:
|
||||
print("Decryption failed")
|
||||
return None
|
|
@ -13,7 +13,7 @@
|
|||
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||
"""
|
||||
|
||||
from decrypt.params import KEYPATH
|
||||
from .params import KEYPATH
|
||||
__license__ = 'GPL v3'
|
||||
__version__ = "10.0.4"
|
||||
|
|
@ -1,118 +1,118 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Use my own small RSA code so we don't have to include the huge
|
||||
python3-rsa just for these small bits.
|
||||
The original code used blinding and this one doesn't,
|
||||
but we don't really care about side-channel attacks ...
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from Cryptodome.PublicKey import RSA
|
||||
except ImportError:
|
||||
# Some distros still ship this as Crypto
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
class CustomRSA:
|
||||
|
||||
@staticmethod
|
||||
def encrypt_for_adobe_signature(signing_key, message):
|
||||
key = RSA.importKey(signing_key)
|
||||
keylen = CustomRSA.byte_size(key.n)
|
||||
padded = CustomRSA.pad_message(message, keylen)
|
||||
payload = CustomRSA.transform_bytes2int(padded)
|
||||
encrypted = CustomRSA.normal_encrypt(key, payload)
|
||||
block = CustomRSA.transform_int2bytes(encrypted, keylen)
|
||||
return bytearray(block)
|
||||
|
||||
@staticmethod
|
||||
def byte_size(number):
|
||||
# type: (int) -> int
|
||||
return (number.bit_length() + 7) // 8
|
||||
|
||||
@staticmethod
|
||||
def pad_message(message, target_len):
|
||||
# type: (bytes, int) -> bytes
|
||||
|
||||
# Padding always uses 0xFF
|
||||
# Returns: 00 01 PADDING 00 MESSAGE
|
||||
|
||||
max_message_length = target_len - 11
|
||||
message_length = len(message)
|
||||
|
||||
if message_length > max_message_length:
|
||||
raise OverflowError("Message too long, has %d bytes but only space for %d" % (message_length, max_message_length))
|
||||
|
||||
padding_len = target_len - message_length - 3
|
||||
|
||||
ret = bytearray(b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00"]))
|
||||
ret.extend(bytes(message))
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def normal_encrypt(key, message):
|
||||
|
||||
if message < 0 or message > key.n:
|
||||
raise ValueError("Invalid message")
|
||||
|
||||
encrypted = pow(message, key.d, key.n)
|
||||
return encrypted
|
||||
|
||||
@staticmethod
|
||||
def py2_int_to_bytes(value, length, big_endian = True):
|
||||
result = []
|
||||
|
||||
for i in range(0, length):
|
||||
result.append(value >> (i * 8) & 0xff)
|
||||
|
||||
if big_endian:
|
||||
result.reverse()
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def py2_bytes_to_int(bytes, big_endian = True):
|
||||
# type: (bytes, bool) -> int
|
||||
|
||||
my_bytes = bytes
|
||||
if not big_endian:
|
||||
my_bytes.reverse()
|
||||
|
||||
result = 0
|
||||
for b in my_bytes:
|
||||
result = result * 256 + int(b)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def transform_bytes2int(raw_bytes):
|
||||
# type: (bytes) -> int
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
return int.from_bytes(raw_bytes, "big", signed=False)
|
||||
|
||||
return CustomRSA.py2_bytes_to_int(raw_bytes, True)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def transform_int2bytes(number, fill_size = 0):
|
||||
# type: (int, int) -> bytes
|
||||
|
||||
if number < 0:
|
||||
raise ValueError("Negative number")
|
||||
|
||||
size = None
|
||||
|
||||
if fill_size > 0:
|
||||
size = fill_size
|
||||
else:
|
||||
size = max(1, CustomRSA.byte_size(number))
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
return number.to_bytes(size, "big")
|
||||
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
'''
|
||||
Use my own small RSA code so we don't have to include the huge
|
||||
python3-rsa just for these small bits.
|
||||
The original code used blinding and this one doesn't,
|
||||
but we don't really care about side-channel attacks ...
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
from Cryptodome.PublicKey import RSA
|
||||
except ImportError:
|
||||
# Some distros still ship this as Crypto
|
||||
from Crypto.PublicKey import RSA
|
||||
|
||||
class CustomRSA:
|
||||
|
||||
@staticmethod
|
||||
def encrypt_for_adobe_signature(signing_key, message):
|
||||
key = RSA.importKey(signing_key)
|
||||
keylen = CustomRSA.byte_size(key.n)
|
||||
padded = CustomRSA.pad_message(message, keylen)
|
||||
payload = CustomRSA.transform_bytes2int(padded)
|
||||
encrypted = CustomRSA.normal_encrypt(key, payload)
|
||||
block = CustomRSA.transform_int2bytes(encrypted, keylen)
|
||||
return bytearray(block)
|
||||
|
||||
@staticmethod
|
||||
def byte_size(number):
|
||||
# type: (int) -> int
|
||||
return (number.bit_length() + 7) // 8
|
||||
|
||||
@staticmethod
|
||||
def pad_message(message, target_len):
|
||||
# type: (bytes, int) -> bytes
|
||||
|
||||
# Padding always uses 0xFF
|
||||
# Returns: 00 01 PADDING 00 MESSAGE
|
||||
|
||||
max_message_length = target_len - 11
|
||||
message_length = len(message)
|
||||
|
||||
if message_length > max_message_length:
|
||||
raise OverflowError("Message too long, has %d bytes but only space for %d" % (message_length, max_message_length))
|
||||
|
||||
padding_len = target_len - message_length - 3
|
||||
|
||||
ret = bytearray(b"".join([b"\x00\x01", padding_len * b"\xff", b"\x00"]))
|
||||
ret.extend(bytes(message))
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def normal_encrypt(key, message):
|
||||
|
||||
if message < 0 or message > key.n:
|
||||
raise ValueError("Invalid message")
|
||||
|
||||
encrypted = pow(message, key.d, key.n)
|
||||
return encrypted
|
||||
|
||||
@staticmethod
|
||||
def py2_int_to_bytes(value, length, big_endian = True):
|
||||
result = []
|
||||
|
||||
for i in range(0, length):
|
||||
result.append(value >> (i * 8) & 0xff)
|
||||
|
||||
if big_endian:
|
||||
result.reverse()
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def py2_bytes_to_int(bytes, big_endian = True):
|
||||
# type: (bytes, bool) -> int
|
||||
|
||||
my_bytes = bytes
|
||||
if not big_endian:
|
||||
my_bytes.reverse()
|
||||
|
||||
result = 0
|
||||
for b in my_bytes:
|
||||
result = result * 256 + int(b)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def transform_bytes2int(raw_bytes):
|
||||
# type: (bytes) -> int
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
return int.from_bytes(raw_bytes, "big", signed=False)
|
||||
|
||||
return CustomRSA.py2_bytes_to_int(raw_bytes, True)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def transform_int2bytes(number, fill_size = 0):
|
||||
# type: (int, int) -> bytes
|
||||
|
||||
if number < 0:
|
||||
raise ValueError("Negative number")
|
||||
|
||||
size = None
|
||||
|
||||
if fill_size > 0:
|
||||
size = fill_size
|
||||
else:
|
||||
size = max(1, CustomRSA.byte_size(number))
|
||||
|
||||
if sys.version_info[0] >= 3:
|
||||
return number.to_bytes(size, "big")
|
||||
|
||||
return CustomRSA.py2_int_to_bytes(number, size, True)
|
|
@ -1,5 +1,5 @@
|
|||
from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from decrypt.params import KEYPATH
|
||||
from .params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from ..decrypt.params import KEYPATH
|
||||
|
||||
keyContent = b'0\x82\x02\\\x02\x01\x00\x02\x81\x81\x00\xad*E\x8e0\n\x91\xd6\xbaj\xc1t3\xc2R2h\xa6\x18\x063i\xfd\x9bR/e\xa6\xec\x87\xab\x11\n\'\xb7\x93\x14\xb6\xbbm\xfa\xf0\xf4\xe8=\x18\xa6\xe9\x15$\xdao\xb3\x8d\xf5\xddT\n\xf5\t<\xe8\xb2\x93k\x02zi\xe6\x86\x10F\x13\xc9m\xcfZ\x83\xe6=\xd6G\xf2/]3\xff\x8ch#\xea|\xa9I\x9a\xf6\xbf\x19\xd9\x10\xe0\x18\xa1\rb\x801k~\xc03f\x84\x07{v\x88\x18\x9bH\x91+o \x90\x9b\xb7\xf5\x02\x03\x01\x00\x01\x02\x81\x80\x05\xfd\x95\xd3\x886\x9a\xba\x8ck\xc1\xb5\xc21\x86\xab\x1a\xa8^\x1af%\x9b\x8a\xc0\x96\xc6\x10}\xb6\xf6\xeb\x80\xc4R\xc2@\x9d\xf9F\xa1\xf7\xe6\x06jPs\xad\xc3w\xd3\xea\xb7\xca\xec\x03\x17\xcf\xff\x01u\x96\x15\n\x0e&\xb0\xc7\x90F\xc4\xdaZ"\xc1)>\xee\x19\xf6\x05\xa5\xba\x00H)\xa8>\x1fC\x02\xd3\xba\xa8){\x06^D\xb4\xfd"\x05\x05\xec\xef\xdb.tbZ8\xabU<,+\xb6\xfaI\x98\xcc7H\xedr\xa9\xfd\x02A\x00\xc27%\xc5\xa0\xff\xd5l\xaa\x7f=\x1dx\xab?\xd8~\xf7v\x1f!\x0cCh\xc9\xb4\x1a\x8b\xb2\xaeC\xa0\xf9\x91\xcc\x99<\x11\xfbQ\xae\x8fG\xb0\xd1b\x0c=\xebR\x19\xb4\x15\xd4\x1c\xbe\xf4\xc7E\xe8\xea\xe1\xb3\x0b\x02A\x00\xe4@\xcb(\xdd\x04F\xe4jT\xe5a\xaaj\xaf=F\xa1\xaf\x1c\xa6F\x93\xc7V1\xd9\xb1\x96\xdb\x1b\xf5\x86\r\xb11\x10\x12\x18\xc5\xee\xaeD\xa3\xc1/\xe3\xf2\x8f\xaf\xad\xda\xe6\t\x8d\x9d\x99z\x04\xeeK\xdb \xff\x02A\x00\xad_\x9d\x90v\xd0\xeb->f\xa7\xa0\x0f\x80\x90V+\xc1\xac\xe8\xcd\x0f\xad}u\xd2\x19\x80k\xd9\xb4\xf5\x96\xd4\xd8\xd8R\x0f\x9bR\xa7\x89\xb0m\xdf\xfc\xaf\x00\xf7y+\x08\xe0\x13\xa25\xb5=\xce\xe2\xc6\x0b\x05Q\x02@\x18\xee\xf7\x02\\\xbaU\xe0\'\xb9da9\xd3s\x97\x16\xfb\x1c|\xdd\xb1\x01\xfd\x99m\xd2\xa0\xf2\xa0\xb6\xba(M\xa0\x98\x82o\xe7\xa2\xdf\x82\xcb\xde\xb3\x80\xbe\xbe\xc5qdep\x11\x85\x15\xbd)6\x16\xad\xd4\x9f\x13\x02@\x0f\x15\xc1Y"b\x19\x81Q\x81\x8d\x006\xe4\xf0e\xa2\xa7\xb8\x98{\x1c\x12\xe0\nw\xbe\x86A-\xd0\x1c7\xf3\x169\xadd3\x85\xaf\x13\x99\x08\x97e)c\xaf\xb1V\xf1\x15\xf6K\r\x16\xb4\xf9\xd1\x10\xe2\x92\xf9'
|
||||
|
|
@ -12,9 +12,9 @@ import os, time, shutil
|
|||
import zipfile
|
||||
from lxml import etree
|
||||
|
||||
from setup.libadobe import sendHTTPRequest_DL2FILE
|
||||
from setup.libadobeFulfill import buildRights, fulfill
|
||||
from setup.libpdf import patch_drm_into_pdf
|
||||
from .libadobe import sendHTTPRequest_DL2FILE
|
||||
from .libadobeFulfill import buildRights, fulfill
|
||||
from .libpdf import patch_drm_into_pdf
|
||||
|
||||
#######################################################################
|
||||
|
|
@ -30,7 +30,7 @@ except ImportError:
|
|||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
from setup.customRSA import CustomRSA
|
||||
from .customRSA import CustomRSA
|
||||
|
||||
from cryptography.hazmat.primitives.serialization.pkcs12 import load_key_and_certificates
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
|
@ -38,7 +38,7 @@ from cryptography.hazmat.primitives import serialization
|
|||
VAR_ACS_SERVER_HTTP = "http://adeactivate.adobe.com/adept"
|
||||
VAR_ACS_SERVER_HTTPS = "https://adeactivate.adobe.com/adept"
|
||||
|
||||
from setup.params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from .params import FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
|
||||
# Lists of different ADE "versions" we know about
|
||||
VAR_VER_SUPP_CONFIG_NAMES = [ "ADE 1.7.2", "ADE 2.0.1", "ADE 3.0.1", "ADE 4.0.3", "ADE 4.5.10", "ADE 4.5.11" ]
|
|
@ -15,12 +15,12 @@ except ImportError:
|
|||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
|
||||
from setup.libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest
|
||||
from setup.libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key
|
||||
from setup.libadobe import get_devkey_path, get_device_path, get_activation_xml_path
|
||||
from setup.libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS
|
||||
from setup.libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP
|
||||
from setup.libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE
|
||||
from .libadobe import addNonce, sign_node, sendRequestDocu, sendHTTPRequest
|
||||
from .libadobe import makeFingerprint, makeSerial, encrypt_with_device_key, decrypt_with_device_key
|
||||
from .libadobe import get_devkey_path, get_device_path, get_activation_xml_path
|
||||
from .libadobe import VAR_VER_SUPP_CONFIG_NAMES, VAR_VER_HOBBES_VERSIONS, VAR_VER_OS_IDENTIFIERS
|
||||
from .libadobe import VAR_VER_ALLOWED_BUILD_IDS_SWITCH_TO, VAR_VER_SUPP_VERSIONS, VAR_ACS_SERVER_HTTP
|
||||
from .libadobe import VAR_ACS_SERVER_HTTPS, VAR_VER_BUILD_IDS, VAR_VER_NEED_HTTPS_BUILD_ID_LIMIT, VAR_VER_ALLOWED_BUILD_IDS_AUTHORIZE
|
||||
|
||||
|
||||
def createDeviceFile(randomSerial, useVersionIndex = 0):
|
||||
|
@ -213,7 +213,7 @@ def createUser(useVersionIndex = 0, authCert = None):
|
|||
def encryptLoginCredentials(username, password, authenticationCertificate):
|
||||
# type: (str, str, str) -> bytes
|
||||
|
||||
from setup.libadobe import devkey_bytes as devkey_adobe
|
||||
from .libadobe import devkey_bytes as devkey_adobe
|
||||
import struct
|
||||
|
||||
if devkey_adobe is not None:
|
|
@ -5,10 +5,10 @@ import time
|
|||
|
||||
#@@CALIBRE_COMPAT_CODE@@
|
||||
|
||||
from setup.libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest
|
||||
from setup.libadobe import get_devkey_path, get_device_path, get_activation_xml_path
|
||||
from setup.libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_HOBBES_VERSIONS
|
||||
from setup.libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER
|
||||
from .libadobe import addNonce, sign_node, get_cert_from_pkcs12, sendRequestDocu, sendRequestDocuRC, sendHTTPRequest
|
||||
from .libadobe import get_devkey_path, get_device_path, get_activation_xml_path
|
||||
from .libadobe import VAR_VER_SUPP_VERSIONS, VAR_VER_HOBBES_VERSIONS
|
||||
from .libadobe import VAR_VER_BUILD_IDS, VAR_VER_USE_DIFFERENT_NOTIFICATION_XML_ORDER
|
||||
|
||||
|
||||
def buildFulfillRequest(acsm):
|
||||
|
@ -143,7 +143,7 @@ def getDecryptedCert(pkcs12_b64_string = None):
|
|||
pkcs12_data = base64.b64decode(pkcs12_b64_string)
|
||||
|
||||
try:
|
||||
from setup.libadobe import devkey_bytes as devkey_adobe
|
||||
from .libadobe import devkey_bytes as devkey_adobe
|
||||
except:
|
||||
pass
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
This is an experimental Python version of libgourou.
|
||||
'''
|
||||
|
||||
from setup.libadobe import createDeviceKeyFile, FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from setup.libadobeAccount import createDeviceFile, createUser, signIn, activateDevice, exportAccountEncryptionKeyDER, getAccountUUID
|
||||
from .libadobe import createDeviceKeyFile, FILE_DEVICEKEY, FILE_DEVICEXML, FILE_ACTIVATIONXML
|
||||
from .libadobeAccount import createDeviceFile, createUser, signIn, activateDevice, exportAccountEncryptionKeyDER, getAccountUUID
|
||||
from os.path import exists
|
||||
|
||||
VAR_MAIL = ""
|
||||
VAR_PASS = ""
|
||||
VAR_VER = 1 # None # 1 for ADE2.0.1, 2 for ADE3.0.1
|
||||
from decrypt.params import KEYPATH
|
||||
from ..decrypt.params import KEYPATH
|
||||
|
||||
#################################################################
|
||||
|
17
README.md
17
README.md
|
@ -30,7 +30,7 @@ This tool is intended for educational purposes only. Its primary aim is to assis
|
|||
## Usage
|
||||
|
||||
```
|
||||
usage: DeGourou.py [-h] [-f [F]] [-u [U]] [-t [T]] [-o [O]] [-la] [-li] [-e [E]] [-p [P]] [-lo]
|
||||
usage: DeGourou [-h] [-f [F]] [-u [U]] [-t [T]] [-o [O]] [-la] [-li] [-e [E]] [-p [P]] [-lo]
|
||||
|
||||
Download and Decrypt an encrypted PDF or EPUB file.
|
||||
|
||||
|
@ -74,17 +74,28 @@ optional arguments:
|
|||
|
||||
C. MacOS user's accordingly with name ```DeGourou.bin```
|
||||
|
||||
### For Middlemans
|
||||
|
||||
1. Install through Pip using
|
||||
|
||||
```
|
||||
pip install git+https://gitea.com/bipinkrish/DeGourou.git
|
||||
```
|
||||
|
||||
2. Use `degourou` in Terminal/CMD
|
||||
|
||||
### For Developers
|
||||
|
||||
1. Clone the repositary or Download zip file and extract it
|
||||
2. Install requirements using pip
|
||||
3. Run "DeGourou.py" file
|
||||
3. Run "DeGourou.py" file in "DeGourou" directory
|
||||
|
||||
|
||||
```
|
||||
git clone https://github.com/bipinkrish/DeGourou.git
|
||||
git clone https://gitea.com/bipinkrish/DeGourou.git
|
||||
cd DeGourou
|
||||
pip install -r requirements.txt
|
||||
cd DeGourou
|
||||
python DeGourou.py
|
||||
```
|
||||
|
||||
|
|
36
setup.py
Normal file
36
setup.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='DeGourou',
|
||||
version='1.3.8',
|
||||
description='Automate the process of getting decrypted ebook from InternetArchive without the need for Adobe Digital Editions and Calibre.',
|
||||
url='https://gitea.com/bipinkrish/DeGourou',
|
||||
author='Bipin krishna',
|
||||
license='GPL3',
|
||||
packages=['DeGourou',"DeGourou/setup","DeGourou/decrypt"],
|
||||
install_requires=['pycryptodomex>=3.17',
|
||||
'cryptography>=41.0.1',
|
||||
'lxml>=4.9.2',
|
||||
'requests>=2.31.0',
|
||||
'charset-normalizer>=3.1.0'
|
||||
],
|
||||
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'degourou = DeGourou.DeGourou:main'
|
||||
]
|
||||
},
|
||||
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Science/Research',
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Operating System :: MacOS',
|
||||
'Operating System :: Microsoft :: Windows',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],
|
||||
)
|
Loading…
Reference in a new issue