Installable from Pip and use in CMD

This commit is contained in:
Bipin 2023-08-28 20:17:34 +05:30
parent 4e61897e17
commit eb7d65bab5
18 changed files with 520 additions and 470 deletions

View file

@ -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
View 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()

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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'

View file

@ -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
#######################################################################

View file

@ -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" ]

View file

@ -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:

View file

@ -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

View file

@ -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
#################################################################

View file

@ -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
View 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',
],
)