DeDRM_tools/DeDRM_plugin/kfxdedrm.py

125 lines
4.0 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2018-03-12 18:35:52 -06:00
# -*- coding: utf-8 -*-
# Engine to remove drm from Kindle KFX ebooks
# 2.0 - Python 3 for calibre 5.0
# 2.1 - Some fixes for debugging
# 2.1.1 - Whitespace!
import os, sys
2018-03-12 18:35:52 -06:00
import shutil
import traceback
2018-03-12 18:35:52 -06:00
import zipfile
from io import BytesIO
#@@CALIBRE_COMPAT_CODE@@
from ion import DrmIon, DrmIonVoucher
2018-03-12 18:35:52 -06:00
__license__ = 'GPL v3'
__version__ = '2.0'
2018-03-12 18:35:52 -06:00
class KFXZipBook:
def __init__(self, infile):
self.infile = infile
self.voucher = None
self.decrypted = {}
def getPIDMetaInfo(self):
return (None, None)
def processBook(self, totalpids):
with zipfile.ZipFile(self.infile, 'r') as zf:
for filename in zf.namelist():
with zf.open(filename) as fh:
data = fh.read(8)
2020-11-27 07:01:18 -07:00
if data != b'\xeaDRMION\xee':
continue
data += fh.read()
2018-03-12 18:35:52 -06:00
if self.voucher is None:
self.decrypt_voucher(totalpids)
print("Decrypting KFX DRMION: {0}".format(filename))
outfile = BytesIO()
2020-11-27 20:20:53 -07:00
DrmIon(BytesIO(data[8:-8]), lambda name: self.voucher).parse(outfile)
2018-03-12 18:35:52 -06:00
self.decrypted[filename] = outfile.getvalue()
if not self.decrypted:
print("The .kfx-zip archive does not contain an encrypted DRMION file")
2018-03-12 18:35:52 -06:00
def decrypt_voucher(self, totalpids):
with zipfile.ZipFile(self.infile, 'r') as zf:
for info in zf.infolist():
with zf.open(info.filename) as fh:
data = fh.read(4)
2020-11-27 07:01:18 -07:00
if data != b'\xe0\x01\x00\xea':
continue
data += fh.read()
2020-11-27 07:01:18 -07:00
if b'ProtectedData' in data:
2018-03-12 18:35:52 -06:00
break # found DRM voucher
else:
raise Exception("The .kfx-zip archive contains an encrypted DRMION file without a DRM voucher")
2018-03-12 18:35:52 -06:00
print("Decrypting KFX DRM voucher: {0}".format(info.filename))
2018-03-12 18:35:52 -06:00
for pid in [''] + totalpids:
# Belt and braces. PIDs should be unicode strings, but just in case...
if isinstance(pid, bytes):
pid = pid.decode('ascii')
2023-12-21 04:35:11 -07:00
for dsn_len,secret_len in [(0,0), (16,0), (16,40), (32,0), (32,40), (40,0), (40,40)]:
2018-03-12 18:35:52 -06:00
if len(pid) == dsn_len + secret_len:
break # split pid into DSN and account secret
else:
continue
try:
2020-11-27 20:20:53 -07:00
voucher = DrmIonVoucher(BytesIO(data), pid[:dsn_len], pid[dsn_len:])
2018-03-12 18:35:52 -06:00
voucher.parse()
voucher.decryptvoucher()
break
except:
traceback.print_exc()
pass
2018-03-12 18:35:52 -06:00
else:
raise Exception("Failed to decrypt KFX DRM voucher with any key")
2018-03-12 18:35:52 -06:00
print("KFX DRM voucher successfully decrypted")
2018-03-12 18:35:52 -06:00
license_type = voucher.getlicensetype()
if license_type != "Purchase":
2021-11-15 05:59:20 -07:00
#raise Exception(("This book is licensed as {0}. "
# 'These tools are intended for use on purchased books.').format(license_type))
print("Warning: This book is licensed as {0}. "
"These tools are intended for use on purchased books. Continuing ...".format(license_type))
2018-03-12 18:35:52 -06:00
self.voucher = voucher
def getBookTitle(self):
return os.path.splitext(os.path.split(self.infile)[1])[0]
def getBookExtension(self):
return '.kfx-zip'
def getBookType(self):
return 'KFX-ZIP'
def cleanup(self):
pass
def getFile(self, outpath):
if not self.decrypted:
shutil.copyfile(self.infile, outpath)
else:
with zipfile.ZipFile(self.infile, 'r') as zif:
with zipfile.ZipFile(outpath, 'w') as zof:
for info in zif.infolist():
zof.writestr(info, self.decrypted.get(info.filename, zif.read(info.filename)))