mirror of
https://github.com/noDRM/DeDRM_tools.git
synced 2024-12-28 12:07:39 +06:00
Remove OpenSSL support; only support PyCryptodome
This allows us to clean up the code a lot. On Windows, it isn't installed by default and most of the time not be found at all. On M1 Macs, the kernel will kill the process instead. Closes #33.
This commit is contained in:
parent
f4634b5eab
commit
a1dd63ae5f
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# adobekey.pyw, version 7.1
|
# adobekey.pyw, version 7.4
|
||||||
# Copyright © 2009-2021 i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2022 i♥cabbages, Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@ -32,6 +32,7 @@
|
|||||||
# 7.1 - Fix "failed to decrypt user key key" error (read username from registry)
|
# 7.1 - Fix "failed to decrypt user key key" error (read username from registry)
|
||||||
# 7.2 - Fix decryption error on Python2 if there's unicode in the username
|
# 7.2 - Fix decryption error on Python2 if there's unicode in the username
|
||||||
# 7.3 - Fix OpenSSL in Wine
|
# 7.3 - Fix OpenSSL in Wine
|
||||||
|
# 7.4 - Remove OpenSSL support to only support PyCryptodome
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Adobe ADEPT user key.
|
Retrieve Adobe ADEPT user key.
|
||||||
@ -125,91 +126,12 @@ if iswindows:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import _winreg as winreg
|
import _winreg as winreg
|
||||||
|
|
||||||
def get_fake_windows_libcrypto_path():
|
try:
|
||||||
# There seems to be a bug in Wine where a `find_library('libcrypto-1_1')`
|
from Cryptodome.Cipher import AES
|
||||||
# will not return the path to the libcrypto-1_1.dll file.
|
from Cryptodome.Util.Padding import unpad
|
||||||
# So if we're on Windows, and we didn't find the libcrypto the normal way,
|
except ImportError:
|
||||||
# lets try a hack-y workaround. It's already over anyways at this
|
from Crypto.Cipher import AES
|
||||||
# point, can't really make it worse.
|
from Crypto.Util.Padding import unpad
|
||||||
import sys, os
|
|
||||||
for p in sys.path:
|
|
||||||
if os.path.isfile(os.path.join(p, "libcrypto-1_1.dll")):
|
|
||||||
return os.path.join(p, "libcrypto-1_1.dll")
|
|
||||||
if os.path.isfile(os.path.join(p, "libeay32.dll")):
|
|
||||||
return os.path.join(p, "libeay32.dll")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes.util import find_library
|
|
||||||
libcrypto = find_library('libcrypto-1_1')
|
|
||||||
if libcrypto is None:
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
if libcrypto is None:
|
|
||||||
libcrypto = get_fake_windows_libcrypto_path()
|
|
||||||
if libcrypto is None:
|
|
||||||
raise ADEPTError('libcrypto not found')
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
AES_MAXNR = 14
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
|
||||||
('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
|
||||||
[c_char_p, c_int, AES_KEY_p])
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
|
||||||
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
|
||||||
c_int])
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, userkey):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise ADEPTError('AES improper key used')
|
|
||||||
key = self._key = AES_KEY()
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
|
||||||
if rv < 0:
|
|
||||||
raise ADEPTError('Failed to initialize AES key')
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
iv = (b"\x00" * self._blocksize)
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise ADEPTError('AES decryption failed')
|
|
||||||
return out.raw
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto_pycrypto():
|
|
||||||
try:
|
|
||||||
from Crypto.Cipher import AES as _AES
|
|
||||||
except (ImportError, ModuleNotFoundError):
|
|
||||||
from Cryptodome.Cipher import AES as _AES
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
|
||||||
def decrypt(self, data):
|
|
||||||
return self._aes.decrypt(data)
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES = None
|
|
||||||
for loader in (_load_crypto_pycrypto, _load_crypto_libcrypto):
|
|
||||||
try:
|
|
||||||
AES = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, ModuleNotFoundError, ADEPTError):
|
|
||||||
pass
|
|
||||||
return AES
|
|
||||||
|
|
||||||
AES = _load_crypto()
|
|
||||||
|
|
||||||
|
|
||||||
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
|
||||||
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
|
||||||
@ -402,8 +324,6 @@ if iswindows:
|
|||||||
CryptUnprotectData = CryptUnprotectData()
|
CryptUnprotectData = CryptUnprotectData()
|
||||||
|
|
||||||
def adeptkeys():
|
def adeptkeys():
|
||||||
if AES is None:
|
|
||||||
raise ADEPTError("PyCrypto or OpenSSL must be installed")
|
|
||||||
root = GetSystemDirectory().split('\\')[0] + '\\'
|
root = GetSystemDirectory().split('\\')[0] + '\\'
|
||||||
serial = GetVolumeSerialNumber(root)
|
serial = GetVolumeSerialNumber(root)
|
||||||
vendor = cpuid0()
|
vendor = cpuid0()
|
||||||
@ -416,7 +336,7 @@ if iswindows:
|
|||||||
try:
|
try:
|
||||||
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
|
regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
|
||||||
device = winreg.QueryValueEx(regkey, 'key')[0]
|
device = winreg.QueryValueEx(regkey, 'key')[0]
|
||||||
except WindowsError, FileNotFoundError:
|
except (WindowsError, FileNotFoundError):
|
||||||
raise ADEPTError("Adobe Digital Editions not activated")
|
raise ADEPTError("Adobe Digital Editions not activated")
|
||||||
keykey = CryptUnprotectData(device, entropy)
|
keykey = CryptUnprotectData(device, entropy)
|
||||||
userkey = None
|
userkey = None
|
||||||
@ -424,7 +344,7 @@ if iswindows:
|
|||||||
names = []
|
names = []
|
||||||
try:
|
try:
|
||||||
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
|
||||||
except WindowsError, FileNotFoundError:
|
except (WindowsError, FileNotFoundError):
|
||||||
raise ADEPTError("Could not locate ADE activation")
|
raise ADEPTError("Could not locate ADE activation")
|
||||||
|
|
||||||
i = -1
|
i = -1
|
||||||
@ -443,7 +363,7 @@ if iswindows:
|
|||||||
for j in range(0, 16):
|
for j in range(0, 16):
|
||||||
try:
|
try:
|
||||||
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
|
||||||
except WindowsError, FileNotFoundError:
|
except (WindowsError, FileNotFoundError):
|
||||||
break
|
break
|
||||||
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
ktype = winreg.QueryValueEx(plkkey, None)[0]
|
||||||
if ktype == 'user':
|
if ktype == 'user':
|
||||||
@ -461,10 +381,7 @@ if iswindows:
|
|||||||
pass
|
pass
|
||||||
if ktype == 'privateLicenseKey':
|
if ktype == 'privateLicenseKey':
|
||||||
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
userkey = winreg.QueryValueEx(plkkey, 'value')[0]
|
||||||
userkey = b64decode(userkey)
|
userkey = unpad(AES.new(keykey, AES.MODE_CBC, b'\x00'*16).decrypt(b64decode(userkey)), 16)[26:]
|
||||||
aes = AES(keykey)
|
|
||||||
userkey = aes.decrypt(userkey)
|
|
||||||
userkey = userkey[26:-ord(userkey[-1:])]
|
|
||||||
# print ("found " + uuid_name + " key: " + str(userkey))
|
# print ("found " + uuid_name + " key: " + str(userkey))
|
||||||
keys.append(userkey)
|
keys.append(userkey)
|
||||||
|
|
||||||
|
@ -23,20 +23,13 @@ import sys, os, time
|
|||||||
import base64, hashlib
|
import base64, hashlib
|
||||||
try:
|
try:
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
except:
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
|
||||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||||
|
|
||||||
def unpad(data):
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
pad_len = ord(data[-1])
|
|
||||||
else:
|
|
||||||
pad_len = data[-1]
|
|
||||||
|
|
||||||
return data[:-pad_len]
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from calibre.constants import iswindows, isosx
|
from calibre.constants import iswindows, isosx
|
||||||
@ -55,7 +48,7 @@ def decrypt_passhash(passhash, fp):
|
|||||||
hash_key = hashlib.sha1(bytearray.fromhex(serial_number + PASS_HASH_SECRET)).digest()[:16]
|
hash_key = hashlib.sha1(bytearray.fromhex(serial_number + PASS_HASH_SECRET)).digest()[:16]
|
||||||
|
|
||||||
encrypted_cc_hash = base64.b64decode(passhash)
|
encrypted_cc_hash = base64.b64decode(passhash)
|
||||||
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
|
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]), 16)
|
||||||
return base64.b64encode(cc_hash).decode("ascii")
|
return base64.b64encode(cc_hash).decode("ascii")
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# androidkindlekey.py
|
# androidkindlekey.py
|
||||||
# Copyright © 2010-20 by Thom, Apprentice Harper et al.
|
# Copyright © 2010-22 by Thom, Apprentice Harper et al.
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
# 1.0 - AmazonSecureStorage.xml decryption to serial number
|
||||||
@ -14,13 +14,14 @@
|
|||||||
# 1.4 - Fix some problems identified by Aldo Bleeker
|
# 1.4 - Fix some problems identified by Aldo Bleeker
|
||||||
# 1.5 - Fix another problem identified by Aldo Bleeker
|
# 1.5 - Fix another problem identified by Aldo Bleeker
|
||||||
# 2.0 - Python 3 compatibility
|
# 2.0 - Python 3 compatibility
|
||||||
|
# 2.1 - Remove OpenSSL support; only support PyCryptodome
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Retrieve Kindle for Android Serial Number.
|
Retrieve Kindle for Android Serial Number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '2.0'
|
__version__ = '2.1'
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@ -33,6 +34,13 @@ from hashlib import md5
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from binascii import a2b_hex, b2a_hex
|
from binascii import a2b_hex, b2a_hex
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES, DES
|
||||||
|
from Cryptodome.Util.Padding import pad, unpad
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES, DES
|
||||||
|
from Crypto.Util.Padding import pad, unpad
|
||||||
|
|
||||||
# Routines common to Mac and PC
|
# Routines common to Mac and PC
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
@ -115,24 +123,16 @@ class AndroidObfuscation(object):
|
|||||||
|
|
||||||
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
key = a2b_hex('0176e04c9408b1702d90be333fd53523')
|
||||||
|
|
||||||
|
def _get_cipher(self):
|
||||||
|
return AES.new(self.key, AES.MODE_ECB)
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
cipher = self._get_cipher()
|
pt = pad(plaintext.encode('utf-8'), 16)
|
||||||
padding = len(self.key) - len(plaintext) % len(self.key)
|
return b2a_hex(self._get_cipher().encrypt(pt))
|
||||||
plaintext += chr(padding) * padding
|
|
||||||
return b2a_hex(cipher.encrypt(plaintext.encode('utf-8')))
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
cipher = self._get_cipher()
|
ct = a2b_hex(ciphertext)
|
||||||
plaintext = cipher.decrypt(a2b_hex(ciphertext))
|
return unpad(self._get_cipher().decrypt(ct), 16)
|
||||||
return plaintext[:-ord(plaintext[-1])]
|
|
||||||
|
|
||||||
def _get_cipher(self):
|
|
||||||
try:
|
|
||||||
from Crypto.Cipher import AES
|
|
||||||
return AES.new(self.key)
|
|
||||||
except ImportError:
|
|
||||||
from aescbc import AES, noPadding
|
|
||||||
return AES(self.key, padding=noPadding())
|
|
||||||
|
|
||||||
class AndroidObfuscationV2(AndroidObfuscation):
|
class AndroidObfuscationV2(AndroidObfuscation):
|
||||||
'''AndroidObfuscationV2
|
'''AndroidObfuscationV2
|
||||||
@ -149,12 +149,7 @@ class AndroidObfuscationV2(AndroidObfuscation):
|
|||||||
self.iv = key[8:16]
|
self.iv = key[8:16]
|
||||||
|
|
||||||
def _get_cipher(self):
|
def _get_cipher(self):
|
||||||
try :
|
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
||||||
from Crypto.Cipher import DES
|
|
||||||
return DES.new(self.key, DES.MODE_CBC, self.iv)
|
|
||||||
except ImportError:
|
|
||||||
from python_des import Des, CBC
|
|
||||||
return Des(self.key, CBC, self.iv)
|
|
||||||
|
|
||||||
def parse_preference(path):
|
def parse_preference(path):
|
||||||
''' parse android's shared preference xml '''
|
''' parse android's shared preference xml '''
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# erdr2pml.py
|
# erdr2pml.py
|
||||||
# Copyright © 2008-2021 The Dark Reverser, Apprentice Harper, noDRM et al.
|
# Copyright © 2008-2022 The Dark Reverser, Apprentice Harper, noDRM et al.
|
||||||
#
|
#
|
||||||
# Changelog
|
# Changelog
|
||||||
#
|
#
|
||||||
@ -65,11 +65,17 @@
|
|||||||
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
# 0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
|
||||||
# 1.00 - Added Python 3 compatibility for calibre 5.0
|
# 1.00 - Added Python 3 compatibility for calibre 5.0
|
||||||
# 1.01 - Bugfixes for standalone version.
|
# 1.01 - Bugfixes for standalone version.
|
||||||
|
# 1.02 - Remove OpenSSL support; only use PyCryptodome
|
||||||
|
|
||||||
__version__='1.00'
|
__version__='1.02'
|
||||||
|
|
||||||
import sys, re
|
import sys, re
|
||||||
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback
|
import struct, binascii, getopt, zlib, os, os.path, urllib, tempfile, traceback, hashlib
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import DES
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import DES
|
||||||
|
|
||||||
#@@CALIBRE_COMPAT_CODE@@
|
#@@CALIBRE_COMPAT_CODE@@
|
||||||
|
|
||||||
@ -136,44 +142,6 @@ def unicode_argv():
|
|||||||
argvencoding = sys.stdin.encoding or "utf-8"
|
argvencoding = sys.stdin.encoding or "utf-8"
|
||||||
return [arg if (isinstance(arg, str) or isinstance(arg,unicode)) else str(arg, argvencoding) for arg in sys.argv]
|
return [arg if (isinstance(arg, str) or isinstance(arg,unicode)) else str(arg, argvencoding) for arg in sys.argv]
|
||||||
|
|
||||||
Des = None
|
|
||||||
if iswindows:
|
|
||||||
# first try with pycrypto
|
|
||||||
import pycrypto_des
|
|
||||||
Des = pycrypto_des.load_pycrypto()
|
|
||||||
if Des == None:
|
|
||||||
# they try with openssl
|
|
||||||
import openssl_des
|
|
||||||
Des = openssl_des.load_libcrypto()
|
|
||||||
else:
|
|
||||||
# first try with openssl
|
|
||||||
import openssl_des
|
|
||||||
Des = openssl_des.load_libcrypto()
|
|
||||||
if Des == None:
|
|
||||||
# then try with pycrypto
|
|
||||||
import pycrypto_des
|
|
||||||
Des = pycrypto_des.load_pycrypto()
|
|
||||||
|
|
||||||
# if that did not work then use pure python implementation
|
|
||||||
# of DES and try to speed it up with Psycho
|
|
||||||
if Des == None:
|
|
||||||
import python_des
|
|
||||||
Des = python_des.Des
|
|
||||||
# Import Psyco if available
|
|
||||||
try:
|
|
||||||
# http://psyco.sourceforge.net
|
|
||||||
import psyco
|
|
||||||
psyco.full()
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
from hashlib import sha1
|
|
||||||
except ImportError:
|
|
||||||
# older Python release
|
|
||||||
import sha
|
|
||||||
sha1 = lambda s: sha.new(s)
|
|
||||||
|
|
||||||
import cgi
|
import cgi
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -253,7 +221,7 @@ class EreaderProcessor(object):
|
|||||||
raise ValueError('incorrect eReader version %d (error 1)' % version)
|
raise ValueError('incorrect eReader version %d (error 1)' % version)
|
||||||
data = self.section_reader(1)
|
data = self.section_reader(1)
|
||||||
self.data = data
|
self.data = data
|
||||||
des = Des(fixKey(data[0:8]))
|
des = DES.new(fixKey(data[0:8]), DES.MODE_ECB)
|
||||||
cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
|
cookie_shuf, cookie_size = struct.unpack('>LL', des.decrypt(data[-8:]))
|
||||||
if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
|
if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
|
||||||
raise ValueError('incorrect eReader version (error 2)')
|
raise ValueError('incorrect eReader version (error 2)')
|
||||||
@ -317,7 +285,7 @@ class EreaderProcessor(object):
|
|||||||
if (self.flags & reqd_flags) != reqd_flags:
|
if (self.flags & reqd_flags) != reqd_flags:
|
||||||
print("Flags: 0x%X" % self.flags)
|
print("Flags: 0x%X" % self.flags)
|
||||||
raise ValueError('incompatible eReader file')
|
raise ValueError('incompatible eReader file')
|
||||||
des = Des(fixKey(user_key))
|
des = DES.new(fixKey(user_key), DES.MODE_ECB)
|
||||||
if version == 259:
|
if version == 259:
|
||||||
if drm_sub_version != 7:
|
if drm_sub_version != 7:
|
||||||
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
|
||||||
@ -393,7 +361,7 @@ class EreaderProcessor(object):
|
|||||||
# return bkinfo
|
# return bkinfo
|
||||||
|
|
||||||
def getText(self):
|
def getText(self):
|
||||||
des = Des(fixKey(self.content_key))
|
des = DES.new(fixKey(self.content_key), DES.MODE_ECB)
|
||||||
r = b''
|
r = b''
|
||||||
for i in range(self.num_text_pages):
|
for i in range(self.num_text_pages):
|
||||||
logging.debug('get page %d', i)
|
logging.debug('get page %d', i)
|
||||||
@ -406,7 +374,7 @@ class EreaderProcessor(object):
|
|||||||
sect = self.section_reader(self.first_footnote_page)
|
sect = self.section_reader(self.first_footnote_page)
|
||||||
fnote_ids = deXOR(sect, 0, self.xortable)
|
fnote_ids = deXOR(sect, 0, self.xortable)
|
||||||
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
# the remaining records of the footnote sections need to be decoded with the content_key and zlib inflated
|
||||||
des = Des(fixKey(self.content_key))
|
des = DES.new(fixKey(self.content_key), DES.MODE_ECB)
|
||||||
for i in range(1,self.num_footnote_pages):
|
for i in range(1,self.num_footnote_pages):
|
||||||
logging.debug('get footnotepage %d', i)
|
logging.debug('get footnotepage %d', i)
|
||||||
id_len = ord(fnote_ids[2])
|
id_len = ord(fnote_ids[2])
|
||||||
@ -430,7 +398,7 @@ class EreaderProcessor(object):
|
|||||||
sect = self.section_reader(self.first_sidebar_page)
|
sect = self.section_reader(self.first_sidebar_page)
|
||||||
sbar_ids = deXOR(sect, 0, self.xortable)
|
sbar_ids = deXOR(sect, 0, self.xortable)
|
||||||
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
# the remaining records of the sidebar sections need to be decoded with the content_key and zlib inflated
|
||||||
des = Des(fixKey(self.content_key))
|
des = DES.new(fixKey(self.content_key), DES.MODE_ECB)
|
||||||
for i in range(1,self.num_sidebar_pages):
|
for i in range(1,self.num_sidebar_pages):
|
||||||
id_len = ord(sbar_ids[2])
|
id_len = ord(sbar_ids[2])
|
||||||
id = sbar_ids[3:3+id_len]
|
id = sbar_ids[3:3+id_len]
|
||||||
|
@ -10,22 +10,16 @@ import os
|
|||||||
import base64
|
import base64
|
||||||
try:
|
try:
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
except:
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
import hashlib
|
import hashlib
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||||
|
|
||||||
def unpad(data):
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
pad_len = ord(data[-1])
|
|
||||||
else:
|
|
||||||
pad_len = data[-1]
|
|
||||||
|
|
||||||
return data[:-pad_len]
|
|
||||||
|
|
||||||
def dump_keys(path_to_adobe_folder):
|
def dump_keys(path_to_adobe_folder):
|
||||||
|
|
||||||
@ -53,7 +47,7 @@ def dump_keys(path_to_adobe_folder):
|
|||||||
|
|
||||||
for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
|
for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
|
||||||
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
||||||
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
|
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]), 16)
|
||||||
hashes.append(base64.b64encode(cc_hash).decode("ascii"))
|
hashes.append(base64.b64encode(cc_hash).decode("ascii"))
|
||||||
#print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
#print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ignoblekeyGenPassHash.py
|
# ignoblekeyGenPassHash.py
|
||||||
# Copyright © 2009-2020 i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2022 i♥cabbages, Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@ -31,19 +31,25 @@
|
|||||||
# 2.7 - Work if TkInter is missing
|
# 2.7 - Work if TkInter is missing
|
||||||
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
# 2.8 - Fix bug in stand-alone use (import tkFileDialog)
|
||||||
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
# 3.0 - Added Python 3 compatibility for calibre 5.0
|
||||||
|
# 3.1 - Remove OpenSSL support, only PyCryptodome is supported now
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Generate Barnes & Noble EPUB user key from name and credit card number.
|
Generate Barnes & Noble EPUB user key from name and credit card number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "3.0"
|
__version__ = "3.1"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import hashlib
|
import hashlib
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
# encoded using "replace" before writing them.
|
# encoded using "replace" before writing them.
|
||||||
@ -114,87 +120,6 @@ def unicode_argv():
|
|||||||
class IGNOBLEError(Exception):
|
class IGNOBLEError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
|
|
||||||
if iswindows:
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
raise IGNOBLEError('libcrypto not found')
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
|
||||||
('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',
|
|
||||||
[c_char_p, c_int, AES_KEY_p])
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
|
||||||
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
|
||||||
c_int])
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, userkey, iv):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
self._iv = iv
|
|
||||||
key = self._key = AES_KEY()
|
|
||||||
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, key)
|
|
||||||
if rv < 0:
|
|
||||||
raise IGNOBLEError('Failed to initialize AES Encrypt key')
|
|
||||||
|
|
||||||
def encrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, self._iv, 1)
|
|
||||||
if rv == 0:
|
|
||||||
raise IGNOBLEError('AES encryption failed')
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto_pycrypto():
|
|
||||||
from Crypto.Cipher import AES as _AES
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, key, iv):
|
|
||||||
self._aes = _AES.new(key, _AES.MODE_CBC, iv)
|
|
||||||
|
|
||||||
def encrypt(self, data):
|
|
||||||
return self._aes.encrypt(data)
|
|
||||||
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES = None
|
|
||||||
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
AES = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, IGNOBLEError):
|
|
||||||
pass
|
|
||||||
return AES
|
|
||||||
|
|
||||||
AES = _load_crypto()
|
|
||||||
|
|
||||||
def normalize_name(name):
|
def normalize_name(name):
|
||||||
return ''.join(x for x in name.lower() if x != ' ')
|
return ''.join(x for x in name.lower() if x != ' ')
|
||||||
|
|
||||||
@ -215,8 +140,7 @@ def generate_key(name, ccn):
|
|||||||
name_sha = hashlib.sha1(name).digest()[:16]
|
name_sha = hashlib.sha1(name).digest()[:16]
|
||||||
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
ccn_sha = hashlib.sha1(ccn).digest()[:16]
|
||||||
both_sha = hashlib.sha1(name + ccn).digest()
|
both_sha = hashlib.sha1(name + ccn).digest()
|
||||||
aes = AES(ccn_sha, name_sha)
|
crypt = AES.new(ccn_sha, AES.MODE_CBC, name_sha).encrypt(both_sha + (b'\x0c' * 0x0c))
|
||||||
crypt = aes.encrypt(both_sha + (b'\x0c' * 0x0c))
|
|
||||||
userkey = hashlib.sha1(crypt).digest()
|
userkey = hashlib.sha1(crypt).digest()
|
||||||
return base64.b64encode(userkey)
|
return base64.b64encode(userkey)
|
||||||
|
|
||||||
@ -226,11 +150,6 @@ def cli_main():
|
|||||||
sys.stderr=SafeUnbuffered(sys.stderr)
|
sys.stderr=SafeUnbuffered(sys.stderr)
|
||||||
argv=unicode_argv()
|
argv=unicode_argv()
|
||||||
progname = os.path.basename(argv[0])
|
progname = os.path.basename(argv[0])
|
||||||
if AES is None:
|
|
||||||
print("%s: This script requires OpenSSL or PyCrypto, which must be installed " \
|
|
||||||
"separately. Read the top-of-script comment for details." % \
|
|
||||||
(progname,))
|
|
||||||
return 1
|
|
||||||
if len(argv) != 4:
|
if len(argv) != 4:
|
||||||
print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
|
||||||
return 1
|
return 1
|
||||||
@ -316,13 +235,6 @@ def gui_main():
|
|||||||
self.status['text'] = "Keyfile successfully generated"
|
self.status['text'] = "Keyfile successfully generated"
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = tkinter.Tk()
|
||||||
if AES is None:
|
|
||||||
root.withdraw()
|
|
||||||
tkinter.messagebox.showerror(
|
|
||||||
"Ignoble EPUB Keyfile Generator",
|
|
||||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
|
||||||
"separately. Read the top-of-script comment for details.")
|
|
||||||
return 1
|
|
||||||
root.title("Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
root.title("Barnes & Noble ePub Keyfile Generator v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(300, 0)
|
root.minsize(300, 0)
|
||||||
|
@ -15,8 +15,10 @@ import apsw
|
|||||||
import base64
|
import base64
|
||||||
try:
|
try:
|
||||||
from Cryptodome.Cipher import AES
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
except:
|
except:
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
import hashlib
|
import hashlib
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@ -24,15 +26,6 @@ from lxml import etree
|
|||||||
NOOK_DATA_FOLDER = "%LOCALAPPDATA%\\Packages\\BarnesNoble.Nook_ahnzqzva31enc\\LocalState"
|
NOOK_DATA_FOLDER = "%LOCALAPPDATA%\\Packages\\BarnesNoble.Nook_ahnzqzva31enc\\LocalState"
|
||||||
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
PASS_HASH_SECRET = "9ca588496a1bc4394553d9e018d70b9e"
|
||||||
|
|
||||||
def unpad(data):
|
|
||||||
|
|
||||||
if sys.version_info[0] == 2:
|
|
||||||
pad_len = ord(data[-1])
|
|
||||||
else:
|
|
||||||
pad_len = data[-1]
|
|
||||||
|
|
||||||
return data[:-pad_len]
|
|
||||||
|
|
||||||
|
|
||||||
def dump_keys(print_result=False):
|
def dump_keys(print_result=False):
|
||||||
db_filename = os.path.expandvars(NOOK_DATA_FOLDER + "\\NookDB.db3")
|
db_filename = os.path.expandvars(NOOK_DATA_FOLDER + "\\NookDB.db3")
|
||||||
@ -64,7 +57,7 @@ def dump_keys(print_result=False):
|
|||||||
|
|
||||||
for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
|
for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
|
||||||
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
encrypted_cc_hash = base64.b64decode(pass_hash.text)
|
||||||
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]))
|
cc_hash = unpad(AES.new(hash_key, AES.MODE_CBC, encrypted_cc_hash[:16]).decrypt(encrypted_cc_hash[16:]), 16)
|
||||||
decrypted_hashes.append((base64.b64encode(cc_hash).decode("ascii")))
|
decrypted_hashes.append((base64.b64encode(cc_hash).decode("ascii")))
|
||||||
if print_result:
|
if print_result:
|
||||||
print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# ineptepub.py
|
# ineptepub.py
|
||||||
# Copyright © 2009-2021 by i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2022 by i♥cabbages, Apprentice Harper et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@ -31,13 +31,14 @@
|
|||||||
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
# 6.6 - Import tkFileDialog, don't assume something else will import it.
|
||||||
# 7.0 - Add Python 3 compatibility for calibre 5.0
|
# 7.0 - Add Python 3 compatibility for calibre 5.0
|
||||||
# 7.1 - Add ignoble support, dropping the dedicated ignobleepub.py script
|
# 7.1 - Add ignoble support, dropping the dedicated ignobleepub.py script
|
||||||
|
# 7.2 - Only support PyCryptodome; clean up the code
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypt Adobe Digital Editions encrypted ePub books.
|
Decrypt Adobe Digital Editions encrypted ePub books.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "7.1"
|
__version__ = "7.2"
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@ -49,6 +50,15 @@ from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
|
|||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES, PKCS1_v1_5
|
||||||
|
from Cryptodome.PublicKey import RSA
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES, PKCS1_v1_5
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
# encoded using "replace" before writing them.
|
# encoded using "replace" before writing them.
|
||||||
@ -120,234 +130,6 @@ class ADEPTError(Exception):
|
|||||||
class ADEPTNewVersionError(Exception):
|
class ADEPTNewVersionError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
|
|
||||||
if iswindows:
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
raise ADEPTError('libcrypto not found')
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
RSA_NO_PADDING = 3
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
class RSA(Structure):
|
|
||||||
pass
|
|
||||||
RSA_p = POINTER(RSA)
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
|
||||||
('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
|
|
||||||
[RSA_p, c_char_pp, c_long])
|
|
||||||
RSA_size = F(c_int, 'RSA_size', [RSA_p])
|
|
||||||
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
|
|
||||||
[c_int, c_char_p, c_char_p, RSA_p, c_int])
|
|
||||||
RSA_free = F(None, 'RSA_free', [RSA_p])
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
|
||||||
[c_char_p, c_int, AES_KEY_p])
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',
|
|
||||||
[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,
|
|
||||||
c_int])
|
|
||||||
|
|
||||||
class RSA(object):
|
|
||||||
def __init__(self, der):
|
|
||||||
buf = create_string_buffer(der)
|
|
||||||
pp = c_char_pp(cast(buf, c_char_p))
|
|
||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
|
||||||
if rsa is None:
|
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
|
||||||
|
|
||||||
def decrypt(self, from_):
|
|
||||||
rsa = self._rsa
|
|
||||||
to = create_string_buffer(RSA_size(rsa))
|
|
||||||
dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
|
|
||||||
RSA_NO_PADDING)
|
|
||||||
if dlen < 0:
|
|
||||||
raise ADEPTError('RSA decryption failed')
|
|
||||||
return to[:dlen]
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self._rsa is not None:
|
|
||||||
RSA_free(self._rsa)
|
|
||||||
self._rsa = None
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, userkey):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise ADEPTError('AES improper key used')
|
|
||||||
return
|
|
||||||
key = self._key = AES_KEY()
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
|
||||||
if rv < 0:
|
|
||||||
raise ADEPTError('Failed to initialize AES key')
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
iv = (b"\x00" * self._blocksize)
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._key, iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise ADEPTError('AES decryption failed')
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
return (AES, RSA)
|
|
||||||
|
|
||||||
def _load_crypto_pycrypto():
|
|
||||||
try:
|
|
||||||
from Cryptodome.Cipher import AES as _AES
|
|
||||||
from Cryptodome.PublicKey import RSA as _RSA
|
|
||||||
from Cryptodome.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
|
||||||
except:
|
|
||||||
from Crypto.Cipher import AES as _AES
|
|
||||||
from Crypto.PublicKey import RSA as _RSA
|
|
||||||
from Crypto.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
|
||||||
|
|
||||||
# ASN.1 parsing code from tlslite
|
|
||||||
class ASN1Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ASN1Parser(object):
|
|
||||||
class Parser(object):
|
|
||||||
def __init__(self, bytes):
|
|
||||||
self.bytes = bytes
|
|
||||||
self.index = 0
|
|
||||||
|
|
||||||
def get(self, length):
|
|
||||||
if self.index + length > len(self.bytes):
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
x = 0
|
|
||||||
for count in range(length):
|
|
||||||
x <<= 8
|
|
||||||
x |= self.bytes[self.index]
|
|
||||||
self.index += 1
|
|
||||||
return x
|
|
||||||
|
|
||||||
def getFixBytes(self, lengthBytes):
|
|
||||||
bytes = self.bytes[self.index : self.index+lengthBytes]
|
|
||||||
self.index += lengthBytes
|
|
||||||
return bytes
|
|
||||||
|
|
||||||
def getVarBytes(self, lengthLength):
|
|
||||||
lengthBytes = self.get(lengthLength)
|
|
||||||
return self.getFixBytes(lengthBytes)
|
|
||||||
|
|
||||||
def getFixList(self, length, lengthList):
|
|
||||||
l = [0] * lengthList
|
|
||||||
for x in range(lengthList):
|
|
||||||
l[x] = self.get(length)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def getVarList(self, length, lengthLength):
|
|
||||||
lengthList = self.get(lengthLength)
|
|
||||||
if lengthList % length != 0:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
lengthList = int(lengthList/length)
|
|
||||||
l = [0] * lengthList
|
|
||||||
for x in range(lengthList):
|
|
||||||
l[x] = self.get(length)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def startLengthCheck(self, lengthLength):
|
|
||||||
self.lengthCheck = self.get(lengthLength)
|
|
||||||
self.indexCheck = self.index
|
|
||||||
|
|
||||||
def setLengthCheck(self, length):
|
|
||||||
self.lengthCheck = length
|
|
||||||
self.indexCheck = self.index
|
|
||||||
|
|
||||||
def stopLengthCheck(self):
|
|
||||||
if (self.index - self.indexCheck) != self.lengthCheck:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
|
|
||||||
def atLengthCheck(self):
|
|
||||||
if (self.index - self.indexCheck) < self.lengthCheck:
|
|
||||||
return False
|
|
||||||
elif (self.index - self.indexCheck) == self.lengthCheck:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
|
|
||||||
def __init__(self, bytes):
|
|
||||||
p = self.Parser(bytes)
|
|
||||||
p.get(1)
|
|
||||||
self.length = self._getASN1Length(p)
|
|
||||||
self.value = p.getFixBytes(self.length)
|
|
||||||
|
|
||||||
def getChild(self, which):
|
|
||||||
p = self.Parser(self.value)
|
|
||||||
for x in range(which+1):
|
|
||||||
markIndex = p.index
|
|
||||||
p.get(1)
|
|
||||||
length = self._getASN1Length(p)
|
|
||||||
p.getFixBytes(length)
|
|
||||||
return ASN1Parser(p.bytes[markIndex:p.index])
|
|
||||||
|
|
||||||
def _getASN1Length(self, p):
|
|
||||||
firstLength = p.get(1)
|
|
||||||
if firstLength<=127:
|
|
||||||
return firstLength
|
|
||||||
else:
|
|
||||||
lengthLength = firstLength & 0x7F
|
|
||||||
return p.get(lengthLength)
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
self._aes = _AES.new(key, _AES.MODE_CBC, b'\x00'*16)
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
return self._aes.decrypt(data)
|
|
||||||
|
|
||||||
class RSA(object):
|
|
||||||
def __init__(self, der):
|
|
||||||
key = ASN1Parser([x for x in der])
|
|
||||||
key = [key.getChild(x).value for x in range(1, 4)]
|
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
|
||||||
self._rsa = _RSA.construct(key)
|
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
|
||||||
total = 0
|
|
||||||
for byte in bytes:
|
|
||||||
total = (total << 8) + byte
|
|
||||||
return total
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
return _PKCS1_v1_5.new(self._rsa).decrypt(data, 172)
|
|
||||||
|
|
||||||
return (AES, RSA)
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES = RSA = None
|
|
||||||
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
AES, RSA = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, ADEPTError):
|
|
||||||
pass
|
|
||||||
return (AES, RSA)
|
|
||||||
|
|
||||||
AES, RSA = _load_crypto()
|
|
||||||
|
|
||||||
META_NAMES = ('mimetype', 'META-INF/rights.xml')
|
META_NAMES = ('mimetype', 'META-INF/rights.xml')
|
||||||
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
||||||
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
'enc': 'http://www.w3.org/2001/04/xmlenc#'}
|
||||||
@ -355,7 +137,7 @@ NSMAP = {'adept': 'http://ns.adobe.com/adept',
|
|||||||
class Decryptor(object):
|
class Decryptor(object):
|
||||||
def __init__(self, bookkey, encryption):
|
def __init__(self, bookkey, encryption):
|
||||||
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
enc = lambda tag: '{%s}%s' % (NSMAP['enc'], tag)
|
||||||
self._aes = AES(bookkey)
|
self._aes = AES.new(bookkey, AES.MODE_CBC, b'\x00'*16)
|
||||||
encryption = etree.fromstring(encryption)
|
encryption = etree.fromstring(encryption)
|
||||||
self._encrypted = encrypted = set()
|
self._encrypted = encrypted = set()
|
||||||
self._otherData = otherData = set()
|
self._otherData = otherData = set()
|
||||||
@ -373,11 +155,11 @@ class Decryptor(object):
|
|||||||
path = path.encode('utf-8')
|
path = path.encode('utf-8')
|
||||||
encrypted.add(path)
|
encrypted.add(path)
|
||||||
json_elements_to_remove.add(elem.getparent().getparent())
|
json_elements_to_remove.add(elem.getparent().getparent())
|
||||||
else:
|
else:
|
||||||
path = path.encode('utf-8')
|
path = path.encode('utf-8')
|
||||||
otherData.add(path)
|
otherData.add(path)
|
||||||
self._has_remaining_xml = True
|
self._has_remaining_xml = True
|
||||||
|
|
||||||
for elem in json_elements_to_remove:
|
for elem in json_elements_to_remove:
|
||||||
elem.getparent().remove(elem)
|
elem.getparent().remove(elem)
|
||||||
|
|
||||||
@ -398,8 +180,8 @@ class Decryptor(object):
|
|||||||
except:
|
except:
|
||||||
# possibly not compressed by zip - just return bytes
|
# possibly not compressed by zip - just return bytes
|
||||||
return bytes
|
return bytes
|
||||||
return decompressed_bytes
|
return decompressed_bytes
|
||||||
|
|
||||||
def decrypt(self, path, data):
|
def decrypt(self, path, data):
|
||||||
if path.encode('utf-8') in self._encrypted:
|
if path.encode('utf-8') in self._encrypted:
|
||||||
data = self._aes.decrypt(data)[16:]
|
data = self._aes.decrypt(data)[16:]
|
||||||
@ -446,49 +228,26 @@ def isPassHashBook(inpath):
|
|||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Checks the license file and returns the UUID the book is licensed for.
|
# 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
|
# This is used so that the Calibre plugin can pick the correct decryption key
|
||||||
# first try without having to loop through all possible keys.
|
# first try without having to loop through all possible keys.
|
||||||
def adeptGetUserUUID(inpath):
|
def adeptGetUserUUID(inpath):
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
try:
|
try:
|
||||||
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
rights = etree.fromstring(inf.read('META-INF/rights.xml'))
|
||||||
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
|
||||||
expr = './/%s' % (adept('user'),)
|
expr = './/%s' % (adept('user'),)
|
||||||
user_uuid = ''.join(rights.findtext(expr))
|
user_uuid = ''.join(rights.findtext(expr))
|
||||||
if user_uuid[:9] != "urn:uuid:":
|
if user_uuid[:9] != "urn:uuid:":
|
||||||
return None
|
return None
|
||||||
return user_uuid[9:]
|
return user_uuid[9:]
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def verify_book_key(bookkey):
|
|
||||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
|
||||||
# Byte not null, invalid result
|
|
||||||
return False
|
|
||||||
|
|
||||||
if ((bookkey[0] != '\x02' and bookkey[0] != 2) and
|
|
||||||
((bookkey[0] != '\x00' and bookkey[0] != 0) or
|
|
||||||
(bookkey[1] != '\x02' and bookkey[1] != 2))):
|
|
||||||
# Key not starting with "00 02" or "02" -> error
|
|
||||||
return False
|
|
||||||
|
|
||||||
keylen = len(bookkey) - 17
|
|
||||||
for i in range(1, keylen):
|
|
||||||
if bookkey[i] == 0 or bookkey[i] == '\x00':
|
|
||||||
# Padding data contains a space - that's not allowed.
|
|
||||||
# Probably bad decryption.
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath):
|
def decryptBook(userkey, inpath, outpath):
|
||||||
if AES is None:
|
|
||||||
raise ADEPTError("PyCrypto or OpenSSL must be installed.")
|
|
||||||
|
|
||||||
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
with closing(ZipFile(open(inpath, 'rb'))) as inf:
|
||||||
namelist = inf.namelist()
|
namelist = inf.namelist()
|
||||||
if 'META-INF/rights.xml' not in namelist or \
|
if 'META-INF/rights.xml' not in namelist or \
|
||||||
@ -508,7 +267,7 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
print("Try getting your distributor to give you a new ACSM file, then open that in an old version of ADE (2.0).")
|
print("Try getting your distributor to give you a new ACSM file, then open that in an old version of ADE (2.0).")
|
||||||
print("If your book distributor is not enforcing the new DRM yet, this will give you a copy with the old DRM.")
|
print("If your book distributor is not enforcing the new DRM yet, this will give you a copy with the old DRM.")
|
||||||
raise ADEPTNewVersionError("Book uses new ADEPT encryption")
|
raise ADEPTNewVersionError("Book uses new ADEPT encryption")
|
||||||
|
|
||||||
if len(bookkey) == 172:
|
if len(bookkey) == 172:
|
||||||
print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
|
||||||
elif len(bookkey) == 64:
|
elif len(bookkey) == 64:
|
||||||
@ -519,28 +278,21 @@ def decryptBook(userkey, inpath, outpath):
|
|||||||
|
|
||||||
if len(bookkey) != 64:
|
if len(bookkey) != 64:
|
||||||
# Normal Adobe ADEPT
|
# Normal Adobe ADEPT
|
||||||
rsa = RSA(userkey)
|
rsakey = RSA.import_key(userkey) # parses the ASN1 structure
|
||||||
bookkey = rsa.decrypt(base64.b64decode(bookkey.encode('ascii')))
|
bookkey = base64.b64decode(bookkey)
|
||||||
|
try:
|
||||||
|
bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
|
||||||
|
except ValueError:
|
||||||
|
bookkey = None
|
||||||
|
|
||||||
# Verify key:
|
if bookkey is None:
|
||||||
if len(bookkey) > 16:
|
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
||||||
# Padded as per RSAES-PKCS1-v1_5
|
return 2
|
||||||
if verify_book_key(bookkey):
|
else:
|
||||||
bookkey = bookkey[-16:]
|
|
||||||
else:
|
|
||||||
print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
|
|
||||||
return 2
|
|
||||||
else:
|
|
||||||
# Adobe PassHash / B&N
|
# Adobe PassHash / B&N
|
||||||
key = base64.b64decode(userkey)[:16]
|
key = base64.b64decode(userkey)[:16]
|
||||||
aes = AES(key)
|
bookkey = base64.b64decode(bookkey)
|
||||||
bookkey = aes.decrypt(base64.b64decode(bookkey))
|
bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
|
||||||
if type(bookkey[-1]) != int:
|
|
||||||
pad = ord(bookkey[-1])
|
|
||||||
else:
|
|
||||||
pad = bookkey[-1]
|
|
||||||
|
|
||||||
bookkey = bookkey[:-pad]
|
|
||||||
|
|
||||||
if len(bookkey) > 16:
|
if len(bookkey) > 16:
|
||||||
bookkey = bookkey[-16:]
|
bookkey = bookkey[-16:]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# ineptpdf.py
|
# ineptpdf.py
|
||||||
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
# Copyright © 2009-2020 by i♥cabbages, Apprentice Harper et al.
|
||||||
# Copyright © 2021 by noDRM
|
# Copyright © 2021-2022 by noDRM et al.
|
||||||
|
|
||||||
# Released under the terms of the GNU General Public Licence, version 3
|
# Released under the terms of the GNU General Public Licence, version 3
|
||||||
# <http://www.gnu.org/licenses/>
|
# <http://www.gnu.org/licenses/>
|
||||||
@ -48,27 +48,37 @@
|
|||||||
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
# 8.0.6 - Replace use of float by Decimal for greater precision, and import tkFileDialog
|
||||||
# 9.0.0 - Add Python 3 compatibility for calibre 5
|
# 9.0.0 - Add Python 3 compatibility for calibre 5
|
||||||
# 9.1.0 - Support for decrypting with owner password, support for V=5, R=5 and R=6 PDF files, support for AES256-encrypted PDFs.
|
# 9.1.0 - Support for decrypting with owner password, support for V=5, R=5 and R=6 PDF files, support for AES256-encrypted PDFs.
|
||||||
|
# 9.1.1 - Only support PyCryptodome; clean up the code
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Decrypts Adobe ADEPT-encrypted PDF files.
|
Decrypts Adobe ADEPT-encrypted PDF files.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "9.1.0"
|
__version__ = "9.1.1"
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
|
import hashlib
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import zlib
|
import zlib
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import itertools
|
import itertools
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES, ARC4, PKCS1_v1_5
|
||||||
|
from Cryptodome.PublicKey import RSA
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES, ARC4, PKCS1_v1_5
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
# encoded using "replace" before writing them.
|
# encoded using "replace" before writing them.
|
||||||
@ -140,313 +150,8 @@ class ADEPTInvalidPasswordError(Exception):
|
|||||||
class ADEPTNewVersionError(Exception):
|
class ADEPTNewVersionError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
def SHA256(message):
|
def SHA256(message):
|
||||||
ctx = hashlib.sha256()
|
return hashlib.sha256(message).digest()
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
raise ADEPTError('libcrypto not found')
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
RSA_NO_PADDING = 3
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
class RC4_KEY(Structure):
|
|
||||||
_fields_ = [('x', c_int), ('y', c_int), ('box', c_int * 256)]
|
|
||||||
RC4_KEY_p = POINTER(RC4_KEY)
|
|
||||||
|
|
||||||
class RSA(Structure):
|
|
||||||
pass
|
|
||||||
RSA_p = POINTER(RSA)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
|
||||||
AES_set_encrypt_key = F(c_int, 'AES_set_encrypt_key',[c_char_p, c_int, AES_KEY_p])
|
|
||||||
|
|
||||||
RC4_set_key = F(None,'RC4_set_key',[RC4_KEY_p, c_int, c_char_p])
|
|
||||||
RC4_crypt = F(None,'RC4',[RC4_KEY_p, c_int, c_char_p, c_char_p])
|
|
||||||
|
|
||||||
d2i_RSAPrivateKey = F(RSA_p, 'd2i_RSAPrivateKey',
|
|
||||||
[RSA_p, c_char_pp, c_long])
|
|
||||||
RSA_size = F(c_int, 'RSA_size', [RSA_p])
|
|
||||||
RSA_private_decrypt = F(c_int, 'RSA_private_decrypt',
|
|
||||||
[c_int, c_char_p, c_char_p, RSA_p, c_int])
|
|
||||||
RSA_free = F(None, 'RSA_free', [RSA_p])
|
|
||||||
|
|
||||||
class RSA(object):
|
|
||||||
def __init__(self, der):
|
|
||||||
buf = create_string_buffer(der)
|
|
||||||
pp = c_char_pp(cast(buf, c_char_p))
|
|
||||||
rsa = self._rsa = d2i_RSAPrivateKey(None, pp, len(der))
|
|
||||||
if rsa is None:
|
|
||||||
raise ADEPTError('Error parsing ADEPT user key DER')
|
|
||||||
|
|
||||||
def decrypt(self, from_):
|
|
||||||
rsa = self._rsa
|
|
||||||
to = create_string_buffer(RSA_size(rsa))
|
|
||||||
dlen = RSA_private_decrypt(len(from_), from_, to, rsa,
|
|
||||||
RSA_NO_PADDING)
|
|
||||||
if dlen < 0:
|
|
||||||
raise ADEPTError('RSA decryption failed')
|
|
||||||
return to[1:dlen]
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if self._rsa is not None:
|
|
||||||
RSA_free(self._rsa)
|
|
||||||
self._rsa = None
|
|
||||||
|
|
||||||
class ARC4(object):
|
|
||||||
@classmethod
|
|
||||||
def new(cls, userkey):
|
|
||||||
self = ARC4()
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
key = self._key = RC4_KEY()
|
|
||||||
RC4_set_key(key, self._blocksize, userkey)
|
|
||||||
return self
|
|
||||||
def __init__(self):
|
|
||||||
self._blocksize = 0
|
|
||||||
self._key = None
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
RC4_crypt(self._key, len(data), data, out)
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
MODE_CBC = 0
|
|
||||||
@classmethod
|
|
||||||
def new(cls, userkey, mode, iv, decrypt=True):
|
|
||||||
self = AES()
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
# mode is ignored since CBCMODE is only thing supported/used so far
|
|
||||||
self._mode = mode
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise ADEPTError('AES improper key used')
|
|
||||||
return
|
|
||||||
keyctx = self._keyctx = AES_KEY()
|
|
||||||
self._iv = iv
|
|
||||||
self._isDecrypt = decrypt
|
|
||||||
if decrypt:
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
|
||||||
else:
|
|
||||||
rv = AES_set_encrypt_key(userkey, len(userkey) * 8, keyctx)
|
|
||||||
if rv < 0:
|
|
||||||
raise ADEPTError('Failed to initialize AES key')
|
|
||||||
return self
|
|
||||||
def __init__(self):
|
|
||||||
self._blocksize = 0
|
|
||||||
self._keyctx = None
|
|
||||||
self._iv = 0
|
|
||||||
self._mode = 0
|
|
||||||
self._isDecrypt = None
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not self._isDecrypt:
|
|
||||||
raise ADEPTError("AES not ready for decryption")
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self._iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise ADEPTError('AES decryption failed')
|
|
||||||
return out.raw
|
|
||||||
def encrypt(self, data):
|
|
||||||
if self._isDecrypt:
|
|
||||||
raise ADEPTError("AES not ready for encryption")
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), self._keyctx, self._iv, 1)
|
|
||||||
if rv == 0:
|
|
||||||
raise ADEPTError('AES decryption failed')
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
return (ARC4, RSA, AES)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_crypto_pycrypto():
|
|
||||||
from Crypto.PublicKey import RSA as _RSA
|
|
||||||
from Crypto.Cipher import ARC4 as _ARC4
|
|
||||||
from Crypto.Cipher import AES as _AES
|
|
||||||
from Crypto.Cipher import PKCS1_v1_5 as _PKCS1_v1_5
|
|
||||||
|
|
||||||
# ASN.1 parsing code from tlslite
|
|
||||||
class ASN1Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class ASN1Parser(object):
|
|
||||||
class Parser(object):
|
|
||||||
def __init__(self, bytes):
|
|
||||||
self.bytes = bytes
|
|
||||||
self.index = 0
|
|
||||||
|
|
||||||
def get(self, length):
|
|
||||||
if self.index + length > len(self.bytes):
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
x = 0
|
|
||||||
for count in range(length):
|
|
||||||
x <<= 8
|
|
||||||
x |= self.bytes[self.index]
|
|
||||||
self.index += 1
|
|
||||||
return x
|
|
||||||
|
|
||||||
def getFixBytes(self, lengthBytes):
|
|
||||||
bytes = self.bytes[self.index : self.index+lengthBytes]
|
|
||||||
self.index += lengthBytes
|
|
||||||
return bytes
|
|
||||||
|
|
||||||
def getVarBytes(self, lengthLength):
|
|
||||||
lengthBytes = self.get(lengthLength)
|
|
||||||
return self.getFixBytes(lengthBytes)
|
|
||||||
|
|
||||||
def getFixList(self, length, lengthList):
|
|
||||||
l = [0] * lengthList
|
|
||||||
for x in range(lengthList):
|
|
||||||
l[x] = self.get(length)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def getVarList(self, length, lengthLength):
|
|
||||||
lengthList = self.get(lengthLength)
|
|
||||||
if lengthList % length != 0:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
lengthList = int(lengthList/length)
|
|
||||||
l = [0] * lengthList
|
|
||||||
for x in range(lengthList):
|
|
||||||
l[x] = self.get(length)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def startLengthCheck(self, lengthLength):
|
|
||||||
self.lengthCheck = self.get(lengthLength)
|
|
||||||
self.indexCheck = self.index
|
|
||||||
|
|
||||||
def setLengthCheck(self, length):
|
|
||||||
self.lengthCheck = length
|
|
||||||
self.indexCheck = self.index
|
|
||||||
|
|
||||||
def stopLengthCheck(self):
|
|
||||||
if (self.index - self.indexCheck) != self.lengthCheck:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
|
|
||||||
def atLengthCheck(self):
|
|
||||||
if (self.index - self.indexCheck) < self.lengthCheck:
|
|
||||||
return False
|
|
||||||
elif (self.index - self.indexCheck) == self.lengthCheck:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
raise ASN1Error("Error decoding ASN.1")
|
|
||||||
|
|
||||||
def __init__(self, bytes):
|
|
||||||
p = self.Parser(bytes)
|
|
||||||
p.get(1)
|
|
||||||
self.length = self._getASN1Length(p)
|
|
||||||
self.value = p.getFixBytes(self.length)
|
|
||||||
|
|
||||||
def getChild(self, which):
|
|
||||||
p = self.Parser(self.value)
|
|
||||||
for x in range(which+1):
|
|
||||||
markIndex = p.index
|
|
||||||
p.get(1)
|
|
||||||
length = self._getASN1Length(p)
|
|
||||||
p.getFixBytes(length)
|
|
||||||
return ASN1Parser(p.bytes[markIndex:p.index])
|
|
||||||
|
|
||||||
def _getASN1Length(self, p):
|
|
||||||
firstLength = p.get(1)
|
|
||||||
if firstLength<=127:
|
|
||||||
return firstLength
|
|
||||||
else:
|
|
||||||
lengthLength = firstLength & 0x7F
|
|
||||||
return p.get(lengthLength)
|
|
||||||
|
|
||||||
class ARC4(object):
|
|
||||||
@classmethod
|
|
||||||
def new(cls, userkey):
|
|
||||||
self = ARC4()
|
|
||||||
self._arc4 = _ARC4.new(userkey)
|
|
||||||
return self
|
|
||||||
def __init__(self):
|
|
||||||
self._arc4 = None
|
|
||||||
def decrypt(self, data):
|
|
||||||
return self._arc4.decrypt(data)
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
MODE_CBC = _AES.MODE_CBC
|
|
||||||
@classmethod
|
|
||||||
def new(cls, userkey, mode, iv, decrypt=True):
|
|
||||||
self = AES()
|
|
||||||
self._aes = _AES.new(userkey, mode, iv)
|
|
||||||
self._decrypt = decrypt
|
|
||||||
return self
|
|
||||||
def __init__(self):
|
|
||||||
self._aes = None
|
|
||||||
self._decrypt = None
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not self._decrypt:
|
|
||||||
raise ADEPTError("AES not ready for decrypt.")
|
|
||||||
|
|
||||||
return self._aes.decrypt(data)
|
|
||||||
def encrypt(self, data):
|
|
||||||
if self._decrypt:
|
|
||||||
raise ADEPTError("AES not ready for encrypt.")
|
|
||||||
return self._aes.encrypt(data)
|
|
||||||
|
|
||||||
class RSA(object):
|
|
||||||
def __init__(self, der):
|
|
||||||
key = ASN1Parser([x for x in der])
|
|
||||||
key = [key.getChild(x).value for x in range(1, 4)]
|
|
||||||
key = [self.bytesToNumber(v) for v in key]
|
|
||||||
self._rsa = _RSA.construct(key)
|
|
||||||
|
|
||||||
def bytesToNumber(self, bytes):
|
|
||||||
total = 0
|
|
||||||
for byte in bytes:
|
|
||||||
total = (total << 8) + byte
|
|
||||||
return total
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
return _PKCS1_v1_5.new(self._rsa).decrypt(data, 172)
|
|
||||||
|
|
||||||
return (ARC4, RSA, AES)
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
ARC4 = RSA = AES = None
|
|
||||||
cryptolist = (_load_crypto_libcrypto, _load_crypto_pycrypto)
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
ARC4, RSA, AES = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, ADEPTError):
|
|
||||||
pass
|
|
||||||
return (ARC4, RSA, AES)
|
|
||||||
ARC4, RSA, AES = _load_crypto()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Do we generate cross reference streams on output?
|
# Do we generate cross reference streams on output?
|
||||||
# 0 = never
|
# 0 = never
|
||||||
@ -668,7 +373,7 @@ class PSBaseParser(object):
|
|||||||
if isinstance(s[j], str):
|
if isinstance(s[j], str):
|
||||||
# Python 2
|
# Python 2
|
||||||
c = s[j]
|
c = s[j]
|
||||||
else:
|
else:
|
||||||
# Python 3
|
# Python 3
|
||||||
c = bytes([s[j]])
|
c = bytes([s[j]])
|
||||||
self.tokenstart = self.bufpos+j
|
self.tokenstart = self.bufpos+j
|
||||||
@ -1593,14 +1298,14 @@ class PDFDocument(object):
|
|||||||
|
|
||||||
def check_user_password(self, password, docid, param):
|
def check_user_password(self, password, docid, param):
|
||||||
V = int_value(param.get('V', 0))
|
V = int_value(param.get('V', 0))
|
||||||
if V < 5:
|
if V < 5:
|
||||||
return self.check_user_password_V4(password, docid, param)
|
return self.check_user_password_V4(password, docid, param)
|
||||||
else:
|
else:
|
||||||
return self.check_user_password_V5(password, param)
|
return self.check_user_password_V5(password, param)
|
||||||
|
|
||||||
def check_owner_password(self, password, docid, param):
|
def check_owner_password(self, password, docid, param):
|
||||||
V = int_value(param.get('V', 0))
|
V = int_value(param.get('V', 0))
|
||||||
if V < 5:
|
if V < 5:
|
||||||
return self.check_owner_password_V4(password, docid, param)
|
return self.check_owner_password_V4(password, docid, param)
|
||||||
else:
|
else:
|
||||||
return self.check_owner_password_V5(password, param)
|
return self.check_owner_password_V5(password, param)
|
||||||
@ -1664,7 +1369,7 @@ class PDFDocument(object):
|
|||||||
return plaintext
|
return plaintext
|
||||||
else:
|
else:
|
||||||
aes = AES.new(key, AES.MODE_CBC, iv, False)
|
aes = AES.new(key, AES.MODE_CBC, iv, False)
|
||||||
new_data = bytes(data * repetitions)
|
new_data = bytes(data * repetitions)
|
||||||
crypt = aes.encrypt(new_data)
|
crypt = aes.encrypt(new_data)
|
||||||
return crypt
|
return crypt
|
||||||
|
|
||||||
@ -1674,7 +1379,7 @@ class PDFDocument(object):
|
|||||||
K = SHA256(password + salt + userdata)
|
K = SHA256(password + salt + userdata)
|
||||||
if R < 6:
|
if R < 6:
|
||||||
return K
|
return K
|
||||||
elif R == 6:
|
elif R == 6:
|
||||||
round_number = 0
|
round_number = 0
|
||||||
done = False
|
done = False
|
||||||
while (not done):
|
while (not done):
|
||||||
@ -1684,24 +1389,7 @@ class PDFDocument(object):
|
|||||||
raise Exception("K1 < 32 ...")
|
raise Exception("K1 < 32 ...")
|
||||||
#def process_with_aes(self, key: bytes, encrypt: bool, data: bytes, repetitions: int = 1, iv: bytes = None):
|
#def process_with_aes(self, key: bytes, encrypt: bool, data: bytes, repetitions: int = 1, iv: bytes = None):
|
||||||
E = self.process_with_aes(K[:16], True, K1, 64, K[16:32])
|
E = self.process_with_aes(K[:16], True, K1, 64, K[16:32])
|
||||||
|
K = (hashlib.sha256, hashlib.sha384, hashlib.sha512)[sum(E) % 3](E).digest()
|
||||||
E_mod_3 = 0
|
|
||||||
for i in range(16):
|
|
||||||
E_mod_3 += E[i]
|
|
||||||
E_mod_3 = E_mod_3 % 3
|
|
||||||
|
|
||||||
if E_mod_3 == 0:
|
|
||||||
ctx = hashlib.sha256()
|
|
||||||
ctx.update(E)
|
|
||||||
K = ctx.digest()
|
|
||||||
elif E_mod_3 == 1:
|
|
||||||
ctx = hashlib.sha384()
|
|
||||||
ctx.update(E)
|
|
||||||
K = ctx.digest()
|
|
||||||
else:
|
|
||||||
ctx = hashlib.sha512()
|
|
||||||
ctx.update(E)
|
|
||||||
K = ctx.digest()
|
|
||||||
|
|
||||||
if round_number >= 64:
|
if round_number >= 64:
|
||||||
ch = int.from_bytes(E[-1:], "big", signed=False)
|
ch = int.from_bytes(E[-1:], "big", signed=False)
|
||||||
@ -1720,7 +1408,7 @@ class PDFDocument(object):
|
|||||||
V = int_value(param.get('V', 0))
|
V = int_value(param.get('V', 0))
|
||||||
if V >= 5:
|
if V >= 5:
|
||||||
raise Exception("compute_O_rc4_key not possible with V>= 5")
|
raise Exception("compute_O_rc4_key not possible with V>= 5")
|
||||||
|
|
||||||
R = int_value(param.get('R', 0))
|
R = int_value(param.get('R', 0))
|
||||||
|
|
||||||
length = int_value(param.get('Length', 40)) # Key length (bits)
|
length = int_value(param.get('Length', 40)) # Key length (bits)
|
||||||
@ -1730,10 +1418,10 @@ class PDFDocument(object):
|
|||||||
for _ in range(50):
|
for _ in range(50):
|
||||||
hash = hashlib.md5(hash.digest()[:length//8])
|
hash = hashlib.md5(hash.digest()[:length//8])
|
||||||
hash = hash.digest()[:length//8]
|
hash = hash.digest()[:length//8]
|
||||||
|
|
||||||
# "hash" is the return value of compute_O_rc4_key
|
# "hash" is the return value of compute_O_rc4_key
|
||||||
|
|
||||||
Odata = str_value(param.get('O'))
|
Odata = str_value(param.get('O'))
|
||||||
# now call iterate_rc4 ...
|
# now call iterate_rc4 ...
|
||||||
x = ARC4.new(hash).decrypt(Odata) # 4
|
x = ARC4.new(hash).decrypt(Odata) # 4
|
||||||
if R >= 3:
|
if R >= 3:
|
||||||
@ -1741,25 +1429,25 @@ class PDFDocument(object):
|
|||||||
k = b''.join(bytes([c ^ i]) for c in hash )
|
k = b''.join(bytes([c ^ i]) for c in hash )
|
||||||
x = ARC4.new(k).decrypt(x)
|
x = ARC4.new(k).decrypt(x)
|
||||||
|
|
||||||
|
|
||||||
# "x" is now the padded user password.
|
# "x" is now the padded user password.
|
||||||
|
|
||||||
# If we wanted to recover / extract the user password,
|
# If we wanted to recover / extract the user password,
|
||||||
# we'd need to trim off the padding string from the end.
|
# we'd need to trim off the padding string from the end.
|
||||||
# As we just want to get access to the encryption key,
|
# As we just want to get access to the encryption key,
|
||||||
# we can just hand the password into the check_user_password
|
# we can just hand the password into the check_user_password
|
||||||
# as it is, as that function would be adding padding anyways.
|
# as it is, as that function would be adding padding anyways.
|
||||||
# This trick only works with V4 and lower.
|
# This trick only works with V4 and lower.
|
||||||
|
|
||||||
enc_key = self.check_user_password(x, docid, param)
|
enc_key = self.check_user_password(x, docid, param)
|
||||||
if enc_key is not None:
|
if enc_key is not None:
|
||||||
return enc_key
|
return enc_key
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def check_user_password_V4(self, password, docid, param):
|
def check_user_password_V4(self, password, docid, param):
|
||||||
|
|
||||||
V = int_value(param.get('V', 0))
|
V = int_value(param.get('V', 0))
|
||||||
@ -1803,10 +1491,10 @@ class PDFDocument(object):
|
|||||||
is_authenticated = (u1 == U)
|
is_authenticated = (u1 == U)
|
||||||
else:
|
else:
|
||||||
is_authenticated = (u1[:16] == U[:16])
|
is_authenticated = (u1[:16] == U[:16])
|
||||||
|
|
||||||
if is_authenticated:
|
if is_authenticated:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def initialize_standard(self, password, docid, param):
|
def initialize_standard(self, password, docid, param):
|
||||||
@ -1842,7 +1530,7 @@ class PDFDocument(object):
|
|||||||
self.decrypt_key = retval
|
self.decrypt_key = retval
|
||||||
|
|
||||||
if self.decrypt_key is None or self.decrypt_key is True or self.decrypt_key is False:
|
if self.decrypt_key is None or self.decrypt_key is True or self.decrypt_key is False:
|
||||||
raise ADEPTInvalidPasswordError("Password invalid.")
|
raise ADEPTInvalidPasswordError("Password invalid.")
|
||||||
|
|
||||||
|
|
||||||
P = int_value(param['P'])
|
P = int_value(param['P'])
|
||||||
@ -1868,8 +1556,8 @@ class PDFDocument(object):
|
|||||||
set_decipher = False
|
set_decipher = False
|
||||||
|
|
||||||
if V >= 4:
|
if V >= 4:
|
||||||
# Check if we need new genkey_v4 - only if we're using AES.
|
# Check if we need new genkey_v4 - only if we're using AES.
|
||||||
try:
|
try:
|
||||||
for key in param['CF']:
|
for key in param['CF']:
|
||||||
algo = str(param["CF"][key]["CFM"])
|
algo = str(param["CF"][key]["CFM"])
|
||||||
if algo == "/AESV2":
|
if algo == "/AESV2":
|
||||||
@ -1893,46 +1581,26 @@ class PDFDocument(object):
|
|||||||
self.decipher = self.decrypt_rc4 # XXX may be AES
|
self.decipher = self.decrypt_rc4 # XXX may be AES
|
||||||
# aes
|
# aes
|
||||||
if not set_decipher:
|
if not set_decipher:
|
||||||
# This should usually already be set by now.
|
# This should usually already be set by now.
|
||||||
# If it's not, assume that V4 and newer are using AES
|
# If it's not, assume that V4 and newer are using AES
|
||||||
if V >= 4:
|
if V >= 4:
|
||||||
self.decipher = self.decrypt_aes
|
self.decipher = self.decrypt_aes
|
||||||
self.ready = True
|
self.ready = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def verify_book_key(self, bookkey):
|
|
||||||
if bookkey[-17] != '\x00' and bookkey[-17] != 0:
|
|
||||||
# Byte not null, invalid result
|
|
||||||
return False
|
|
||||||
|
|
||||||
if ((bookkey[0] != '\x02' and bookkey[0] != 2) and
|
|
||||||
((bookkey[0] != '\x00' and bookkey[0] != 0) or
|
|
||||||
(bookkey[1] != '\x02' and bookkey[1] != 2))):
|
|
||||||
# Key not starting with "00 02" or "02" -> error
|
|
||||||
return False
|
|
||||||
|
|
||||||
keylen = len(bookkey) - 17
|
|
||||||
for i in range(1, keylen):
|
|
||||||
if bookkey[i] == 0 or bookkey[i] == '\x00':
|
|
||||||
# Padding data contains a space - that's not allowed.
|
|
||||||
# Probably bad decryption.
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def initialize_ebx_ignoble(self, keyb64, docid, param):
|
def initialize_ebx_ignoble(self, keyb64, docid, param):
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
key = keyb64.decode('base64')[:16]
|
key = keyb64.decode('base64')[:16]
|
||||||
aes = AES.new(key,AES.MODE_CBC,"\x00" * len(key))
|
|
||||||
length = int_value(param.get('Length', 0)) / 8
|
length = int_value(param.get('Length', 0)) / 8
|
||||||
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
|
rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
|
||||||
rights = zlib.decompress(rights, -15)
|
rights = zlib.decompress(rights, -15)
|
||||||
rights = etree.fromstring(rights)
|
rights = etree.fromstring(rights)
|
||||||
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
expr = './/{http://ns.adobe.com/adept}encryptedKey'
|
||||||
bookkey = ''.join(rights.findtext(expr)).decode('base64')
|
bookkey = ''.join(rights.findtext(expr)).decode('base64')
|
||||||
bookkey = aes.decrypt(bookkey)
|
bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
|
||||||
bookkey = bookkey[:-ord(bookkey[-1])]
|
if len(bookkey) > 16:
|
||||||
bookkey = bookkey[-16:]
|
bookkey = bookkey[-16:]
|
||||||
ebx_V = int_value(param.get('V', 4))
|
ebx_V = int_value(param.get('V', 4))
|
||||||
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
||||||
# added because of improper booktype / decryption book session key errors
|
# added because of improper booktype / decryption book session key errors
|
||||||
@ -1967,7 +1635,7 @@ class PDFDocument(object):
|
|||||||
|
|
||||||
def initialize_ebx_inept(self, password, docid, param):
|
def initialize_ebx_inept(self, password, docid, param):
|
||||||
self.is_printable = self.is_modifiable = self.is_extractable = True
|
self.is_printable = self.is_modifiable = self.is_extractable = True
|
||||||
rsa = RSA(password)
|
rsakey = RSA.import_key(password) # parses the ASN1 structure
|
||||||
length = int_value(param.get('Length', 0)) // 8
|
length = int_value(param.get('Length', 0)) // 8
|
||||||
rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
|
rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
|
||||||
rights = zlib.decompress(rights, -15)
|
rights = zlib.decompress(rights, -15)
|
||||||
@ -1983,14 +1651,13 @@ class PDFDocument(object):
|
|||||||
raise ADEPTNewVersionError("Book uses new ADEPT encryption")
|
raise ADEPTNewVersionError("Book uses new ADEPT encryption")
|
||||||
|
|
||||||
bookkey = codecs.decode(bookkey.encode('utf-8'),'base64')
|
bookkey = codecs.decode(bookkey.encode('utf-8'),'base64')
|
||||||
bookkey = rsa.decrypt(bookkey)
|
try:
|
||||||
|
bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
|
||||||
|
except ValueError:
|
||||||
|
bookkey = None
|
||||||
|
|
||||||
if len(bookkey) > 16:
|
if bookkey is None:
|
||||||
if (self.verify_book_key(bookkey)):
|
raise ADEPTError('error decrypting book session key')
|
||||||
bookkey = bookkey[-16:]
|
|
||||||
length = 16
|
|
||||||
else:
|
|
||||||
raise ADEPTError('error decrypting book session key')
|
|
||||||
|
|
||||||
ebx_V = int_value(param.get('V', 4))
|
ebx_V = int_value(param.get('V', 4))
|
||||||
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
|
||||||
@ -2357,7 +2024,7 @@ class PDFObjStrmParser(PDFParser):
|
|||||||
# Takes a PDF file name as input, and if this is an ADE-protected PDF,
|
# Takes a PDF file name as input, and if this is an ADE-protected PDF,
|
||||||
# returns the UUID of the user that's licensed to open this file.
|
# returns the UUID of the user that's licensed to open this file.
|
||||||
def adeptGetUserUUID(inf):
|
def adeptGetUserUUID(inf):
|
||||||
try:
|
try:
|
||||||
doc = PDFDocument()
|
doc = PDFDocument()
|
||||||
inf = open(inf, 'rb')
|
inf = open(inf, 'rb')
|
||||||
pars = PDFParser(doc, inf)
|
pars = PDFParser(doc, inf)
|
||||||
@ -2376,11 +2043,11 @@ def adeptGetUserUUID(inf):
|
|||||||
rights = etree.fromstring(rights)
|
rights = etree.fromstring(rights)
|
||||||
expr = './/{http://ns.adobe.com/adept}user'
|
expr = './/{http://ns.adobe.com/adept}user'
|
||||||
user_uuid = ''.join(rights.findtext(expr))
|
user_uuid = ''.join(rights.findtext(expr))
|
||||||
if user_uuid[:9] != "urn:uuid:":
|
if user_uuid[:9] != "urn:uuid:":
|
||||||
return None
|
return None
|
||||||
return user_uuid[9:]
|
return user_uuid[9:]
|
||||||
|
|
||||||
except:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
###
|
###
|
||||||
@ -2585,8 +2252,6 @@ class PDFSerializer(object):
|
|||||||
|
|
||||||
|
|
||||||
def decryptBook(userkey, inpath, outpath, inept=True):
|
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:
|
with open(inpath, 'rb') as inf:
|
||||||
serializer = PDFSerializer(inf, userkey, inept)
|
serializer = PDFSerializer(inf, userkey, inept)
|
||||||
with open(outpath, 'wb') as outf:
|
with open(outpath, 'wb') as outf:
|
||||||
@ -2601,8 +2266,6 @@ def decryptBook(userkey, inpath, outpath, inept=True):
|
|||||||
|
|
||||||
|
|
||||||
def getPDFencryptionType(inpath):
|
def getPDFencryptionType(inpath):
|
||||||
if RSA is None:
|
|
||||||
raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
|
|
||||||
with open(inpath, 'rb') as inf:
|
with open(inpath, 'rb') as inf:
|
||||||
doc = doc = PDFDocument()
|
doc = doc = PDFDocument()
|
||||||
parser = PDFParser(doc, inf)
|
parser = PDFParser(doc, inf)
|
||||||
@ -2735,13 +2398,6 @@ def gui_main():
|
|||||||
|
|
||||||
|
|
||||||
root = tkinter.Tk()
|
root = tkinter.Tk()
|
||||||
if RSA is None:
|
|
||||||
root.withdraw()
|
|
||||||
tkinter.messagebox.showerror(
|
|
||||||
"INEPT PDF",
|
|
||||||
"This script requires OpenSSL or PyCrypto, which must be installed "
|
|
||||||
"separately. Read the top-of-script comment for details.")
|
|
||||||
return 1
|
|
||||||
root.title("Adobe Adept PDF Decrypter v.{0}".format(__version__))
|
root.title("Adobe Adept PDF Decrypter v.{0}".format(__version__))
|
||||||
root.resizable(True, False)
|
root.resizable(True, False)
|
||||||
root.minsize(370, 0)
|
root.minsize(370, 0)
|
||||||
|
@ -30,8 +30,14 @@ import struct
|
|||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from Crypto.Cipher import AES
|
try:
|
||||||
from Crypto.Util.py3compat import bchr
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Util.py3compat import bchr
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
from Crypto.Util.py3compat import bchr
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# lzma library from calibre 4.6.0 or later
|
# lzma library from calibre 4.6.0 or later
|
||||||
@ -744,23 +750,6 @@ def addprottable(ion):
|
|||||||
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
ion.addtocatalog("ProtectedData", 1, SYM_NAMES)
|
||||||
|
|
||||||
|
|
||||||
def pkcs7pad(msg, blocklen):
|
|
||||||
paddinglen = blocklen - len(msg) % blocklen
|
|
||||||
padding = bchr(paddinglen) * paddinglen
|
|
||||||
return msg + padding
|
|
||||||
|
|
||||||
|
|
||||||
def pkcs7unpad(msg, blocklen):
|
|
||||||
_assert(len(msg) % blocklen == 0)
|
|
||||||
|
|
||||||
paddinglen = msg[-1]
|
|
||||||
|
|
||||||
_assert(paddinglen > 0 and paddinglen <= blocklen, "Incorrect padding - Wrong key")
|
|
||||||
_assert(msg[-paddinglen:] == bchr(paddinglen) * paddinglen, "Incorrect padding - Wrong key")
|
|
||||||
|
|
||||||
return msg[:-paddinglen]
|
|
||||||
|
|
||||||
|
|
||||||
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
# every VoucherEnvelope version has a corresponding "word" and magic number, used in obfuscating the shared secret
|
||||||
OBFUSCATION_TABLE = {
|
OBFUSCATION_TABLE = {
|
||||||
"V1": (0x00, None),
|
"V1": (0x00, None),
|
||||||
@ -876,7 +865,7 @@ class DrmIonVoucher(object):
|
|||||||
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
|
||||||
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
|
||||||
b = aes.decrypt(self.ciphertext)
|
b = aes.decrypt(self.ciphertext)
|
||||||
b = pkcs7unpad(b, 16)
|
b = unpad(b, 16)
|
||||||
|
|
||||||
self.drmkey = BinaryIonParser(BytesIO(b))
|
self.drmkey = BinaryIonParser(BytesIO(b))
|
||||||
addprottable(self.drmkey)
|
addprottable(self.drmkey)
|
||||||
@ -1082,7 +1071,7 @@ class DrmIon(object):
|
|||||||
def processpage(self, ct, civ, outpages, decompress, decrypt):
|
def processpage(self, ct, civ, outpages, decompress, decrypt):
|
||||||
if decrypt:
|
if decrypt:
|
||||||
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
|
||||||
msg = pkcs7unpad(aes.decrypt(ct), 16)
|
msg = unpad(aes.decrypt(ct), 16)
|
||||||
else:
|
else:
|
||||||
msg = ct
|
msg = ct
|
||||||
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# kindlekey.py
|
# kindlekey.py
|
||||||
# Copyright © 2008-2020 Apprentice Harper et al.
|
# Copyright © 2008-2022 Apprentice Harper et al.
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = '3.0'
|
__version__ = '3.1'
|
||||||
|
|
||||||
# Revision history:
|
# Revision history:
|
||||||
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
# 1.0 - Kindle info file decryption, extracted from k4mobidedrm, etc.
|
||||||
@ -30,6 +30,7 @@ __version__ = '3.0'
|
|||||||
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
# 2.7 - Finish .kinf2018 support, PC & Mac by Apprentice Sakuya
|
||||||
# 2.8 - Fix for Mac OS X Big Sur
|
# 2.8 - Fix for Mac OS X Big Sur
|
||||||
# 3.0 - Python 3 for calibre 5.0
|
# 3.0 - Python 3 for calibre 5.0
|
||||||
|
# 3.1 - Only support PyCryptodome; clean up the code
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -42,6 +43,16 @@ from struct import pack, unpack, unpack_from
|
|||||||
import json
|
import json
|
||||||
import getopt
|
import getopt
|
||||||
import traceback
|
import traceback
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Util import Counter
|
||||||
|
from Cryptodome.Protocol.KDF import PBKDF2
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util import Counter
|
||||||
|
from Crypto.Protocol.KDF import PBKDF2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
RegError
|
RegError
|
||||||
@ -121,22 +132,16 @@ class DrmException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# crypto digestroutines
|
# crypto digestroutines
|
||||||
import hashlib
|
|
||||||
|
|
||||||
def MD5(message):
|
def MD5(message):
|
||||||
ctx = hashlib.md5()
|
return hashlib.md5(message).digest()
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
def SHA1(message):
|
def SHA1(message):
|
||||||
ctx = hashlib.sha1()
|
return hashlib.sha1(message).digest()
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
def SHA256(message):
|
def SHA256(message):
|
||||||
ctx = hashlib.sha256()
|
return hashlib.sha256(message).digest()
|
||||||
ctx.update(message)
|
|
||||||
return ctx.digest()
|
|
||||||
|
|
||||||
# For K4M/PC 1.6.X and later
|
# For K4M/PC 1.6.X and later
|
||||||
def primes(n):
|
def primes(n):
|
||||||
@ -189,6 +194,12 @@ def decode(data,map):
|
|||||||
result += pack('B',value)
|
result += pack('B',value)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def UnprotectHeaderData(encryptedData):
|
||||||
|
passwdData = b'header_key_data'
|
||||||
|
salt = b'HEADER.2011'
|
||||||
|
key_iv = PBKDF2(passwdData, salt, dkLen=256, count=128)
|
||||||
|
return AES.new(key_iv[0:32], AES.MODE_CBC, key_iv[32:48]).decrypt(encryptedData)
|
||||||
|
|
||||||
# Routines unique to Mac and PC
|
# Routines unique to Mac and PC
|
||||||
if iswindows:
|
if iswindows:
|
||||||
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
|
||||||
@ -205,636 +216,6 @@ if iswindows:
|
|||||||
advapi32 = windll.advapi32
|
advapi32 = windll.advapi32
|
||||||
crypt32 = windll.crypt32
|
crypt32 = windll.crypt32
|
||||||
|
|
||||||
try:
|
|
||||||
# try to get fast routines from alfcrypto
|
|
||||||
from alfcrypto import AES_CBC, KeyIVGen
|
|
||||||
except:
|
|
||||||
# alfcrypto not available, so use python implementations
|
|
||||||
"""
|
|
||||||
Routines for doing AES CBC in one file
|
|
||||||
|
|
||||||
Modified by some_updates to extract
|
|
||||||
and combine only those parts needed for AES CBC
|
|
||||||
into one simple to add python file
|
|
||||||
|
|
||||||
Original Version
|
|
||||||
Copyright (c) 2002 by Paul A. Lambert
|
|
||||||
Under:
|
|
||||||
CryptoPy Artistic License Version 1.0
|
|
||||||
See the wonderful pure python package cryptopy-1.2.5
|
|
||||||
and read its LICENSE.txt for complete license details.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class CryptoError(Exception):
|
|
||||||
""" Base class for crypto exceptions """
|
|
||||||
def __init__(self,errorMessage='Error!'):
|
|
||||||
self.message = errorMessage
|
|
||||||
def __str__(self):
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
class InitCryptoError(CryptoError):
|
|
||||||
""" Crypto errors during algorithm initialization """
|
|
||||||
class BadKeySizeError(InitCryptoError):
|
|
||||||
""" Bad key size error """
|
|
||||||
class EncryptError(CryptoError):
|
|
||||||
""" Error in encryption processing """
|
|
||||||
class DecryptError(CryptoError):
|
|
||||||
""" Error in decryption processing """
|
|
||||||
class DecryptNotBlockAlignedError(DecryptError):
|
|
||||||
""" Error in decryption processing """
|
|
||||||
|
|
||||||
def xor(a,b):
|
|
||||||
""" XOR two byte arrays, to lesser length """
|
|
||||||
x = []
|
|
||||||
for i in range(min(len(a),len(b))):
|
|
||||||
x.append( a[i] ^ b[i])
|
|
||||||
return bytes(x)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Base 'BlockCipher' and Pad classes for cipher instances.
|
|
||||||
BlockCipher supports automatic padding and type conversion. The BlockCipher
|
|
||||||
class was written to make the actual algorithm code more readable and
|
|
||||||
not for performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class BlockCipher:
|
|
||||||
""" Block ciphers """
|
|
||||||
def __init__(self):
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.resetEncrypt()
|
|
||||||
self.resetDecrypt()
|
|
||||||
def resetEncrypt(self):
|
|
||||||
self.encryptBlockCount = 0
|
|
||||||
self.bytesToEncrypt = b''
|
|
||||||
def resetDecrypt(self):
|
|
||||||
self.decryptBlockCount = 0
|
|
||||||
self.bytesToDecrypt = b''
|
|
||||||
|
|
||||||
def encrypt(self, plainText, more = None):
|
|
||||||
""" Encrypt a string and return a binary string """
|
|
||||||
self.bytesToEncrypt += plainText # append plainText to any bytes from prior encrypt
|
|
||||||
numBlocks, numExtraBytes = divmod(len(self.bytesToEncrypt), self.blockSize)
|
|
||||||
cipherText = ''
|
|
||||||
for i in range(numBlocks):
|
|
||||||
bStart = i*self.blockSize
|
|
||||||
ctBlock = self.encryptBlock(self.bytesToEncrypt[bStart:bStart+self.blockSize])
|
|
||||||
self.encryptBlockCount += 1
|
|
||||||
cipherText += ctBlock
|
|
||||||
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
||||||
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
||||||
else:
|
|
||||||
self.bytesToEncrypt = ''
|
|
||||||
|
|
||||||
if more == None: # no more data expected from caller
|
|
||||||
finalBytes = self.padding.addPad(self.bytesToEncrypt,self.blockSize)
|
|
||||||
if len(finalBytes) > 0:
|
|
||||||
ctBlock = self.encryptBlock(finalBytes)
|
|
||||||
self.encryptBlockCount += 1
|
|
||||||
cipherText += ctBlock
|
|
||||||
self.resetEncrypt()
|
|
||||||
return cipherText
|
|
||||||
|
|
||||||
def decrypt(self, cipherText, more = None):
|
|
||||||
""" Decrypt a string and return a string """
|
|
||||||
self.bytesToDecrypt += cipherText # append to any bytes from prior decrypt
|
|
||||||
|
|
||||||
numBlocks, numExtraBytes = divmod(len(self.bytesToDecrypt), self.blockSize)
|
|
||||||
if more == None: # no more calls to decrypt, should have all the data
|
|
||||||
if numExtraBytes != 0:
|
|
||||||
raise DecryptNotBlockAlignedError('Data not block aligned on decrypt')
|
|
||||||
|
|
||||||
# hold back some bytes in case last decrypt has zero len
|
|
||||||
if (more != None) and (numExtraBytes == 0) and (numBlocks >0) :
|
|
||||||
numBlocks -= 1
|
|
||||||
numExtraBytes = self.blockSize
|
|
||||||
|
|
||||||
plainText = b''
|
|
||||||
for i in range(numBlocks):
|
|
||||||
bStart = i*self.blockSize
|
|
||||||
ptBlock = self.decryptBlock(self.bytesToDecrypt[bStart : bStart+self.blockSize])
|
|
||||||
self.decryptBlockCount += 1
|
|
||||||
plainText += ptBlock
|
|
||||||
|
|
||||||
if numExtraBytes > 0: # save any bytes that are not block aligned
|
|
||||||
self.bytesToEncrypt = self.bytesToEncrypt[-numExtraBytes:]
|
|
||||||
else:
|
|
||||||
self.bytesToEncrypt = ''
|
|
||||||
|
|
||||||
if more == None: # last decrypt remove padding
|
|
||||||
plainText = self.padding.removePad(plainText, self.blockSize)
|
|
||||||
self.resetDecrypt()
|
|
||||||
return plainText
|
|
||||||
|
|
||||||
|
|
||||||
class Pad:
|
|
||||||
def __init__(self):
|
|
||||||
pass # eventually could put in calculation of min and max size extension
|
|
||||||
|
|
||||||
class padWithPadLen(Pad):
|
|
||||||
""" Pad a binary string with the length of the padding """
|
|
||||||
|
|
||||||
def addPad(self, extraBytes, blockSize):
|
|
||||||
""" Add padding to a binary string to make it an even multiple
|
|
||||||
of the block size """
|
|
||||||
blocks, numExtraBytes = divmod(len(extraBytes), blockSize)
|
|
||||||
padLength = blockSize - numExtraBytes
|
|
||||||
return extraBytes + padLength*chr(padLength)
|
|
||||||
|
|
||||||
def removePad(self, paddedBinaryString, blockSize):
|
|
||||||
""" Remove padding from a binary string """
|
|
||||||
if not(0<len(paddedBinaryString)):
|
|
||||||
raise DecryptNotBlockAlignedError('Expected More Data')
|
|
||||||
return paddedBinaryString[:-ord(paddedBinaryString[-1])]
|
|
||||||
|
|
||||||
class noPadding(Pad):
|
|
||||||
""" No padding. Use this to get ECB behavior from encrypt/decrypt """
|
|
||||||
|
|
||||||
def addPad(self, extraBytes, blockSize):
|
|
||||||
""" Add no padding """
|
|
||||||
return extraBytes
|
|
||||||
|
|
||||||
def removePad(self, paddedBinaryString, blockSize):
|
|
||||||
""" Remove no padding """
|
|
||||||
return paddedBinaryString
|
|
||||||
|
|
||||||
"""
|
|
||||||
Rijndael encryption algorithm
|
|
||||||
This byte oriented implementation is intended to closely
|
|
||||||
match FIPS specification for readability. It is not implemented
|
|
||||||
for performance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Rijndael(BlockCipher):
|
|
||||||
""" Rijndael encryption algorithm """
|
|
||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16, blockSize=16 ):
|
|
||||||
self.name = 'RIJNDAEL'
|
|
||||||
self.keySize = keySize
|
|
||||||
self.strength = keySize*8
|
|
||||||
self.blockSize = blockSize # blockSize is in bytes
|
|
||||||
self.padding = padding # change default to noPadding() to get normal ECB behavior
|
|
||||||
|
|
||||||
assert( keySize%4==0 and (keySize//4) in NrTable[4]),'key size must be 16,20,24,29 or 32 bytes'
|
|
||||||
assert( blockSize%4==0 and (blockSize//4) in NrTable), 'block size must be 16,20,24,29 or 32 bytes'
|
|
||||||
|
|
||||||
self.Nb = self.blockSize//4 # Nb is number of columns of 32 bit words
|
|
||||||
self.Nk = keySize//4 # Nk is the key length in 32-bit words
|
|
||||||
self.Nr = NrTable[self.Nb][self.Nk] # The number of rounds (Nr) is a function of
|
|
||||||
# the block (Nb) and key (Nk) sizes.
|
|
||||||
if key != None:
|
|
||||||
self.setKey(key)
|
|
||||||
|
|
||||||
def setKey(self, key):
|
|
||||||
""" Set a key and generate the expanded key """
|
|
||||||
assert( len(key) == (self.Nk*4) ), 'Key length must be same as keySize parameter'
|
|
||||||
self.__expandedKey = keyExpansion(self, key)
|
|
||||||
self.reset() # BlockCipher.reset()
|
|
||||||
|
|
||||||
def encryptBlock(self, plainTextBlock):
|
|
||||||
""" Encrypt a block, plainTextBlock must be a array of bytes [Nb by 4] """
|
|
||||||
self.state = self._toBlock(plainTextBlock)
|
|
||||||
AddRoundKey(self, self.__expandedKey[0:self.Nb])
|
|
||||||
for round in range(1,self.Nr): #for round = 1 step 1 to Nr
|
|
||||||
SubBytes(self)
|
|
||||||
ShiftRows(self)
|
|
||||||
MixColumns(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
|
|
||||||
SubBytes(self)
|
|
||||||
ShiftRows(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
|
|
||||||
return self._toBString(self.state)
|
|
||||||
|
|
||||||
|
|
||||||
def decryptBlock(self, encryptedBlock):
|
|
||||||
""" decrypt a block (array of bytes) """
|
|
||||||
self.state = self._toBlock(encryptedBlock)
|
|
||||||
AddRoundKey(self, self.__expandedKey[self.Nr*self.Nb:(self.Nr+1)*self.Nb])
|
|
||||||
for round in range(self.Nr-1,0,-1):
|
|
||||||
InvShiftRows(self)
|
|
||||||
InvSubBytes(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[round*self.Nb:(round+1)*self.Nb])
|
|
||||||
InvMixColumns(self)
|
|
||||||
InvShiftRows(self)
|
|
||||||
InvSubBytes(self)
|
|
||||||
AddRoundKey(self, self.__expandedKey[0:self.Nb])
|
|
||||||
return self._toBString(self.state)
|
|
||||||
|
|
||||||
def _toBlock(self, bs):
|
|
||||||
""" Convert binary string to array of bytes, state[col][row]"""
|
|
||||||
assert ( len(bs) == 4*self.Nb ), 'Rijndarl blocks must be of size blockSize'
|
|
||||||
return [[bs[4*i],bs[4*i+1],bs[4*i+2],bs[4*i+3]] for i in range(self.Nb)]
|
|
||||||
|
|
||||||
def _toBString(self, block):
|
|
||||||
""" Convert block (array of bytes) to binary string """
|
|
||||||
l = []
|
|
||||||
for col in block:
|
|
||||||
for rowElement in col:
|
|
||||||
l.append(rowElement)
|
|
||||||
return bytes(l)
|
|
||||||
#-------------------------------------
|
|
||||||
""" Number of rounds Nr = NrTable[Nb][Nk]
|
|
||||||
|
|
||||||
Nb Nk=4 Nk=5 Nk=6 Nk=7 Nk=8
|
|
||||||
------------------------------------- """
|
|
||||||
NrTable = {4: {4:10, 5:11, 6:12, 7:13, 8:14},
|
|
||||||
5: {4:11, 5:11, 6:12, 7:13, 8:14},
|
|
||||||
6: {4:12, 5:12, 6:12, 7:13, 8:14},
|
|
||||||
7: {4:13, 5:13, 6:13, 7:13, 8:14},
|
|
||||||
8: {4:14, 5:14, 6:14, 7:14, 8:14}}
|
|
||||||
#-------------------------------------
|
|
||||||
def keyExpansion(algInstance, keyArray):
|
|
||||||
""" Expand a byte array of size keySize into a larger array """
|
|
||||||
Nk, Nb, Nr = algInstance.Nk, algInstance.Nb, algInstance.Nr # for readability
|
|
||||||
w = [[keyArray[4*i],keyArray[4*i+1],keyArray[4*i+2],keyArray[4*i+3]] for i in range(Nk)]
|
|
||||||
for i in range(Nk,Nb*(Nr+1)):
|
|
||||||
temp = w[i-1] # a four byte column
|
|
||||||
if (i%Nk) == 0 :
|
|
||||||
temp = temp[1:]+[temp[0]] # RotWord(temp)
|
|
||||||
temp = [ Sbox[byte] for byte in temp ]
|
|
||||||
temp[0] ^= Rcon[i//Nk]
|
|
||||||
elif Nk > 6 and i%Nk == 4 :
|
|
||||||
temp = [ Sbox[byte] for byte in temp ] # SubWord(temp)
|
|
||||||
w.append( [ w[i-Nk][byte]^temp[byte] for byte in range(4) ] )
|
|
||||||
return w
|
|
||||||
|
|
||||||
Rcon = (0,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36, # note extra '0' !!!
|
|
||||||
0x6c,0xd8,0xab,0x4d,0x9a,0x2f,0x5e,0xbc,0x63,0xc6,
|
|
||||||
0x97,0x35,0x6a,0xd4,0xb3,0x7d,0xfa,0xef,0xc5,0x91)
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
def AddRoundKey(algInstance, keyBlock):
|
|
||||||
""" XOR the algorithm state with a block of key material """
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] ^= keyBlock[column][row]
|
|
||||||
#-------------------------------------
|
|
||||||
|
|
||||||
def SubBytes(algInstance):
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] = Sbox[algInstance.state[column][row]]
|
|
||||||
|
|
||||||
def InvSubBytes(algInstance):
|
|
||||||
for column in range(algInstance.Nb):
|
|
||||||
for row in range(4):
|
|
||||||
algInstance.state[column][row] = InvSbox[algInstance.state[column][row]]
|
|
||||||
|
|
||||||
Sbox = (0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,
|
|
||||||
0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
|
|
||||||
0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,
|
|
||||||
0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
|
|
||||||
0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,
|
|
||||||
0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
|
|
||||||
0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,
|
|
||||||
0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
|
|
||||||
0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,
|
|
||||||
0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
|
|
||||||
0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,
|
|
||||||
0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
|
|
||||||
0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,
|
|
||||||
0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
|
|
||||||
0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,
|
|
||||||
0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
|
|
||||||
0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,
|
|
||||||
0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
|
|
||||||
0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,
|
|
||||||
0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
|
|
||||||
0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,
|
|
||||||
0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
|
|
||||||
0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,
|
|
||||||
0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
|
|
||||||
0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,
|
|
||||||
0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
|
|
||||||
0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,
|
|
||||||
0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
|
|
||||||
0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,
|
|
||||||
0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
|
|
||||||
0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,
|
|
||||||
0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16)
|
|
||||||
|
|
||||||
InvSbox = (0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,
|
|
||||||
0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
|
|
||||||
0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,
|
|
||||||
0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
|
|
||||||
0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,
|
|
||||||
0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
|
|
||||||
0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,
|
|
||||||
0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
|
|
||||||
0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,
|
|
||||||
0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
|
|
||||||
0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,
|
|
||||||
0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
|
|
||||||
0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,
|
|
||||||
0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
|
|
||||||
0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,
|
|
||||||
0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
|
|
||||||
0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,
|
|
||||||
0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
|
|
||||||
0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,
|
|
||||||
0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
|
|
||||||
0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,
|
|
||||||
0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
|
|
||||||
0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,
|
|
||||||
0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
|
|
||||||
0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,
|
|
||||||
0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
|
|
||||||
0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,
|
|
||||||
0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
|
|
||||||
0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,
|
|
||||||
0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
|
|
||||||
0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,
|
|
||||||
0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d)
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
""" For each block size (Nb), the ShiftRow operation shifts row i
|
|
||||||
by the amount Ci. Note that row 0 is not shifted.
|
|
||||||
Nb C1 C2 C3
|
|
||||||
------------------- """
|
|
||||||
shiftOffset = { 4 : ( 0, 1, 2, 3),
|
|
||||||
5 : ( 0, 1, 2, 3),
|
|
||||||
6 : ( 0, 1, 2, 3),
|
|
||||||
7 : ( 0, 1, 2, 4),
|
|
||||||
8 : ( 0, 1, 3, 4) }
|
|
||||||
def ShiftRows(algInstance):
|
|
||||||
tmp = [0]*algInstance.Nb # list of size Nb
|
|
||||||
for r in range(1,4): # row 0 reamains unchanged and can be skipped
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
tmp[c] = algInstance.state[(c+shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
algInstance.state[c][r] = tmp[c]
|
|
||||||
def InvShiftRows(algInstance):
|
|
||||||
tmp = [0]*algInstance.Nb # list of size Nb
|
|
||||||
for r in range(1,4): # row 0 reamains unchanged and can be skipped
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
tmp[c] = algInstance.state[(c+algInstance.Nb-shiftOffset[algInstance.Nb][r]) % algInstance.Nb][r]
|
|
||||||
for c in range(algInstance.Nb):
|
|
||||||
algInstance.state[c][r] = tmp[c]
|
|
||||||
#-------------------------------------
|
|
||||||
def MixColumns(a):
|
|
||||||
Sprime = [0,0,0,0]
|
|
||||||
for j in range(a.Nb): # for each column
|
|
||||||
Sprime[0] = mul(2,a.state[j][0])^mul(3,a.state[j][1])^mul(1,a.state[j][2])^mul(1,a.state[j][3])
|
|
||||||
Sprime[1] = mul(1,a.state[j][0])^mul(2,a.state[j][1])^mul(3,a.state[j][2])^mul(1,a.state[j][3])
|
|
||||||
Sprime[2] = mul(1,a.state[j][0])^mul(1,a.state[j][1])^mul(2,a.state[j][2])^mul(3,a.state[j][3])
|
|
||||||
Sprime[3] = mul(3,a.state[j][0])^mul(1,a.state[j][1])^mul(1,a.state[j][2])^mul(2,a.state[j][3])
|
|
||||||
for i in range(4):
|
|
||||||
a.state[j][i] = Sprime[i]
|
|
||||||
|
|
||||||
def InvMixColumns(a):
|
|
||||||
""" Mix the four bytes of every column in a linear way
|
|
||||||
This is the opposite operation of Mixcolumn """
|
|
||||||
Sprime = [0,0,0,0]
|
|
||||||
for j in range(a.Nb): # for each column
|
|
||||||
Sprime[0] = mul(0x0E,a.state[j][0])^mul(0x0B,a.state[j][1])^mul(0x0D,a.state[j][2])^mul(0x09,a.state[j][3])
|
|
||||||
Sprime[1] = mul(0x09,a.state[j][0])^mul(0x0E,a.state[j][1])^mul(0x0B,a.state[j][2])^mul(0x0D,a.state[j][3])
|
|
||||||
Sprime[2] = mul(0x0D,a.state[j][0])^mul(0x09,a.state[j][1])^mul(0x0E,a.state[j][2])^mul(0x0B,a.state[j][3])
|
|
||||||
Sprime[3] = mul(0x0B,a.state[j][0])^mul(0x0D,a.state[j][1])^mul(0x09,a.state[j][2])^mul(0x0E,a.state[j][3])
|
|
||||||
for i in range(4):
|
|
||||||
a.state[j][i] = Sprime[i]
|
|
||||||
|
|
||||||
#-------------------------------------
|
|
||||||
def mul(a, b):
|
|
||||||
""" Multiply two elements of GF(2^m)
|
|
||||||
needed for MixColumn and InvMixColumn """
|
|
||||||
if (a !=0 and b!=0):
|
|
||||||
return Alogtable[(Logtable[a] + Logtable[b])%255]
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
Logtable = ( 0, 0, 25, 1, 50, 2, 26, 198, 75, 199, 27, 104, 51, 238, 223, 3,
|
|
||||||
100, 4, 224, 14, 52, 141, 129, 239, 76, 113, 8, 200, 248, 105, 28, 193,
|
|
||||||
125, 194, 29, 181, 249, 185, 39, 106, 77, 228, 166, 114, 154, 201, 9, 120,
|
|
||||||
101, 47, 138, 5, 33, 15, 225, 36, 18, 240, 130, 69, 53, 147, 218, 142,
|
|
||||||
150, 143, 219, 189, 54, 208, 206, 148, 19, 92, 210, 241, 64, 70, 131, 56,
|
|
||||||
102, 221, 253, 48, 191, 6, 139, 98, 179, 37, 226, 152, 34, 136, 145, 16,
|
|
||||||
126, 110, 72, 195, 163, 182, 30, 66, 58, 107, 40, 84, 250, 133, 61, 186,
|
|
||||||
43, 121, 10, 21, 155, 159, 94, 202, 78, 212, 172, 229, 243, 115, 167, 87,
|
|
||||||
175, 88, 168, 80, 244, 234, 214, 116, 79, 174, 233, 213, 231, 230, 173, 232,
|
|
||||||
44, 215, 117, 122, 235, 22, 11, 245, 89, 203, 95, 176, 156, 169, 81, 160,
|
|
||||||
127, 12, 246, 111, 23, 196, 73, 236, 216, 67, 31, 45, 164, 118, 123, 183,
|
|
||||||
204, 187, 62, 90, 251, 96, 177, 134, 59, 82, 161, 108, 170, 85, 41, 157,
|
|
||||||
151, 178, 135, 144, 97, 190, 220, 252, 188, 149, 207, 205, 55, 63, 91, 209,
|
|
||||||
83, 57, 132, 60, 65, 162, 109, 71, 20, 42, 158, 93, 86, 242, 211, 171,
|
|
||||||
68, 17, 146, 217, 35, 32, 46, 137, 180, 124, 184, 38, 119, 153, 227, 165,
|
|
||||||
103, 74, 237, 222, 197, 49, 254, 24, 13, 99, 140, 128, 192, 247, 112, 7)
|
|
||||||
|
|
||||||
Alogtable= ( 1, 3, 5, 15, 17, 51, 85, 255, 26, 46, 114, 150, 161, 248, 19, 53,
|
|
||||||
95, 225, 56, 72, 216, 115, 149, 164, 247, 2, 6, 10, 30, 34, 102, 170,
|
|
||||||
229, 52, 92, 228, 55, 89, 235, 38, 106, 190, 217, 112, 144, 171, 230, 49,
|
|
||||||
83, 245, 4, 12, 20, 60, 68, 204, 79, 209, 104, 184, 211, 110, 178, 205,
|
|
||||||
76, 212, 103, 169, 224, 59, 77, 215, 98, 166, 241, 8, 24, 40, 120, 136,
|
|
||||||
131, 158, 185, 208, 107, 189, 220, 127, 129, 152, 179, 206, 73, 219, 118, 154,
|
|
||||||
181, 196, 87, 249, 16, 48, 80, 240, 11, 29, 39, 105, 187, 214, 97, 163,
|
|
||||||
254, 25, 43, 125, 135, 146, 173, 236, 47, 113, 147, 174, 233, 32, 96, 160,
|
|
||||||
251, 22, 58, 78, 210, 109, 183, 194, 93, 231, 50, 86, 250, 21, 63, 65,
|
|
||||||
195, 94, 226, 61, 71, 201, 64, 192, 91, 237, 44, 116, 156, 191, 218, 117,
|
|
||||||
159, 186, 213, 100, 172, 239, 42, 126, 130, 157, 188, 223, 122, 142, 137, 128,
|
|
||||||
155, 182, 193, 88, 232, 35, 101, 175, 234, 37, 111, 177, 200, 67, 197, 84,
|
|
||||||
252, 31, 33, 99, 165, 244, 7, 9, 27, 45, 119, 153, 176, 203, 70, 202,
|
|
||||||
69, 207, 74, 222, 121, 139, 134, 145, 168, 227, 62, 66, 198, 81, 243, 14,
|
|
||||||
18, 54, 90, 238, 41, 123, 141, 140, 143, 138, 133, 148, 167, 242, 13, 23,
|
|
||||||
57, 75, 221, 124, 132, 151, 162, 253, 28, 36, 108, 180, 199, 82, 246, 1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
AES Encryption Algorithm
|
|
||||||
The AES algorithm is just Rijndael algorithm restricted to the default
|
|
||||||
blockSize of 128 bits.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class AES(Rijndael):
|
|
||||||
""" The AES algorithm is the Rijndael block cipher restricted to block
|
|
||||||
sizes of 128 bits and key sizes of 128, 192 or 256 bits
|
|
||||||
"""
|
|
||||||
def __init__(self, key = None, padding = padWithPadLen(), keySize=16):
|
|
||||||
""" Initialize AES, keySize is in bytes """
|
|
||||||
if not (keySize == 16 or keySize == 24 or keySize == 32) :
|
|
||||||
raise BadKeySizeError('Illegal AES key size, must be 16, 24, or 32 bytes')
|
|
||||||
|
|
||||||
Rijndael.__init__( self, key, padding=padding, keySize=keySize, blockSize=16 )
|
|
||||||
|
|
||||||
self.name = 'AES'
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
CBC mode of encryption for block ciphers.
|
|
||||||
This algorithm mode wraps any BlockCipher to make a
|
|
||||||
Cipher Block Chaining mode.
|
|
||||||
"""
|
|
||||||
from random import Random # should change to crypto.random!!!
|
|
||||||
|
|
||||||
|
|
||||||
class CBC(BlockCipher):
|
|
||||||
""" The CBC class wraps block ciphers to make cipher block chaining (CBC) mode
|
|
||||||
algorithms. The initialization (IV) is automatic if set to None. Padding
|
|
||||||
is also automatic based on the Pad class used to initialize the algorithm
|
|
||||||
"""
|
|
||||||
def __init__(self, blockCipherInstance, padding = padWithPadLen()):
|
|
||||||
""" CBC algorithms are created by initializing with a BlockCipher instance """
|
|
||||||
self.baseCipher = blockCipherInstance
|
|
||||||
self.name = self.baseCipher.name + '_CBC'
|
|
||||||
self.blockSize = self.baseCipher.blockSize
|
|
||||||
self.keySize = self.baseCipher.keySize
|
|
||||||
self.padding = padding
|
|
||||||
self.baseCipher.padding = noPadding() # baseCipher should NOT pad!!
|
|
||||||
self.r = Random() # for IV generation, currently uses
|
|
||||||
# mediocre standard distro version <----------------
|
|
||||||
import time
|
|
||||||
newSeed = time.ctime()+str(self.r) # seed with instance location
|
|
||||||
self.r.seed(newSeed) # to make unique
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def setKey(self, key):
|
|
||||||
self.baseCipher.setKey(key)
|
|
||||||
|
|
||||||
# Overload to reset both CBC state and the wrapped baseCipher
|
|
||||||
def resetEncrypt(self):
|
|
||||||
BlockCipher.resetEncrypt(self) # reset CBC encrypt state (super class)
|
|
||||||
self.baseCipher.resetEncrypt() # reset base cipher encrypt state
|
|
||||||
|
|
||||||
def resetDecrypt(self):
|
|
||||||
BlockCipher.resetDecrypt(self) # reset CBC state (super class)
|
|
||||||
self.baseCipher.resetDecrypt() # reset base cipher decrypt state
|
|
||||||
|
|
||||||
def encrypt(self, plainText, iv=None, more=None):
|
|
||||||
""" CBC encryption - overloads baseCipher to allow optional explicit IV
|
|
||||||
when iv=None, iv is auto generated!
|
|
||||||
"""
|
|
||||||
if self.encryptBlockCount == 0:
|
|
||||||
self.iv = iv
|
|
||||||
else:
|
|
||||||
assert(iv==None), 'IV used only on first call to encrypt'
|
|
||||||
|
|
||||||
return BlockCipher.encrypt(self,plainText, more=more)
|
|
||||||
|
|
||||||
def decrypt(self, cipherText, iv=None, more=None):
|
|
||||||
""" CBC decryption - overloads baseCipher to allow optional explicit IV
|
|
||||||
when iv=None, iv is auto generated!
|
|
||||||
"""
|
|
||||||
if self.decryptBlockCount == 0:
|
|
||||||
self.iv = iv
|
|
||||||
else:
|
|
||||||
assert(iv==None), 'IV used only on first call to decrypt'
|
|
||||||
|
|
||||||
return BlockCipher.decrypt(self, cipherText, more=more)
|
|
||||||
|
|
||||||
def encryptBlock(self, plainTextBlock):
|
|
||||||
""" CBC block encryption, IV is set with 'encrypt' """
|
|
||||||
auto_IV = ''
|
|
||||||
if self.encryptBlockCount == 0:
|
|
||||||
if self.iv == None:
|
|
||||||
# generate IV and use
|
|
||||||
self.iv = ''.join([chr(self.r.randrange(256)) for i in range(self.blockSize)])
|
|
||||||
self.prior_encr_CT_block = self.iv
|
|
||||||
auto_IV = self.prior_encr_CT_block # prepend IV if it's automatic
|
|
||||||
else: # application provided IV
|
|
||||||
assert(len(self.iv) == self.blockSize ),'IV must be same length as block'
|
|
||||||
self.prior_encr_CT_block = self.iv
|
|
||||||
""" encrypt the prior CT XORed with the PT """
|
|
||||||
ct = self.baseCipher.encryptBlock( xor(self.prior_encr_CT_block, plainTextBlock) )
|
|
||||||
self.prior_encr_CT_block = ct
|
|
||||||
return auto_IV+ct
|
|
||||||
|
|
||||||
def decryptBlock(self, encryptedBlock):
|
|
||||||
""" Decrypt a single block """
|
|
||||||
|
|
||||||
if self.decryptBlockCount == 0: # first call, process IV
|
|
||||||
if self.iv == None: # auto decrypt IV?
|
|
||||||
self.prior_CT_block = encryptedBlock
|
|
||||||
return b''
|
|
||||||
else:
|
|
||||||
assert(len(self.iv)==self.blockSize),"Bad IV size on CBC decryption"
|
|
||||||
self.prior_CT_block = self.iv
|
|
||||||
|
|
||||||
dct = self.baseCipher.decryptBlock(encryptedBlock)
|
|
||||||
""" XOR the prior decrypted CT with the prior CT """
|
|
||||||
dct_XOR_priorCT = xor( self.prior_CT_block, dct )
|
|
||||||
|
|
||||||
self.prior_CT_block = encryptedBlock
|
|
||||||
|
|
||||||
return dct_XOR_priorCT
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
AES_CBC Encryption Algorithm
|
|
||||||
"""
|
|
||||||
|
|
||||||
class aescbc_AES_CBC(CBC):
|
|
||||||
""" AES encryption in CBC feedback mode """
|
|
||||||
def __init__(self, key=None, padding=padWithPadLen(), keySize=16):
|
|
||||||
CBC.__init__( self, AES(key, noPadding(), keySize), padding)
|
|
||||||
self.name = 'AES_CBC'
|
|
||||||
|
|
||||||
class AES_CBC(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._key = None
|
|
||||||
self._iv = None
|
|
||||||
self.aes = None
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._key = userkey
|
|
||||||
self._iv = iv
|
|
||||||
self.aes = aescbc_AES_CBC(userkey, noPadding(), len(userkey))
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
iv = self._iv
|
|
||||||
cleartext = self.aes.decrypt(iv + data)
|
|
||||||
return cleartext
|
|
||||||
|
|
||||||
import hmac
|
|
||||||
|
|
||||||
class KeyIVGen(object):
|
|
||||||
# this only exists in openssl so we will use pure python implementation instead
|
|
||||||
# PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
|
||||||
# [c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
|
||||||
def pbkdf2(self, passwd, salt, iter, keylen):
|
|
||||||
|
|
||||||
def xorbytes( a, b ):
|
|
||||||
if len(a) != len(b):
|
|
||||||
raise Exception("xorbytes(): lengths differ")
|
|
||||||
return bytes([x ^ y for x, y in zip(a, b)])
|
|
||||||
|
|
||||||
def prf( h, data ):
|
|
||||||
hm = h.copy()
|
|
||||||
hm.update( data )
|
|
||||||
return hm.digest()
|
|
||||||
|
|
||||||
def pbkdf2_F( h, salt, itercount, blocknum ):
|
|
||||||
U = prf( h, salt + pack('>i',blocknum ) )
|
|
||||||
T = U
|
|
||||||
for i in range(2, itercount+1):
|
|
||||||
U = prf( h, U )
|
|
||||||
T = xorbytes( T, U )
|
|
||||||
return T
|
|
||||||
|
|
||||||
sha = hashlib.sha1
|
|
||||||
digest_size = sha().digest_size
|
|
||||||
# l - number of output blocks to produce
|
|
||||||
l = keylen // digest_size
|
|
||||||
if keylen % digest_size != 0:
|
|
||||||
l += 1
|
|
||||||
h = hmac.new( passwd, None, sha )
|
|
||||||
T = b""
|
|
||||||
for i in range(1, l+1):
|
|
||||||
T += pbkdf2_F( h, salt, iter, i )
|
|
||||||
return T[0: keylen]
|
|
||||||
|
|
||||||
def UnprotectHeaderData(encryptedData):
|
|
||||||
passwdData = b'header_key_data'
|
|
||||||
salt = b'HEADER.2011'
|
|
||||||
iter = 0x80
|
|
||||||
keylen = 0x100
|
|
||||||
key_iv = KeyIVGen().pbkdf2(passwdData, salt, iter, keylen)
|
|
||||||
key = key_iv[0:32]
|
|
||||||
iv = key_iv[32:48]
|
|
||||||
aes=AES_CBC()
|
|
||||||
aes.set_decrypt_key(key, iv)
|
|
||||||
cleartext = aes.decrypt(encryptedData)
|
|
||||||
return cleartext
|
|
||||||
|
|
||||||
# Various character maps used to decrypt kindle info values.
|
# Various character maps used to decrypt kindle info values.
|
||||||
# Probably supposed to act as obfuscation
|
# Probably supposed to act as obfuscation
|
||||||
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
|
||||||
@ -1080,7 +461,7 @@ if iswindows:
|
|||||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
||||||
sp = GetUserName() + b'+@#$%+' + GetIDString().encode('utf-8')
|
sp = GetUserName() + b'+@#$%+' + GetIDString().encode('utf-8')
|
||||||
passwd = encode(SHA256(sp), charMap5)
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
key = KeyIVGen().pbkdf2(passwd, salt, 10000, 0x400)[:32] # this is very slow
|
key = PBKDF2(passwd, salt, count=10000, dkLen=0x400)[:32] # this is very slow
|
||||||
|
|
||||||
# loop through the item records until all are processed
|
# loop through the item records until all are processed
|
||||||
while len(items) > 0:
|
while len(items) > 0:
|
||||||
@ -1143,8 +524,6 @@ if iswindows:
|
|||||||
entropy = SHA1(keyhash) + added_entropy
|
entropy = SHA1(keyhash) + added_entropy
|
||||||
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
|
||||||
elif version == 6:
|
elif version == 6:
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Util import Counter
|
|
||||||
# decode using new testMap8 to get IV + ciphertext
|
# decode using new testMap8 to get IV + ciphertext
|
||||||
iv_ciphertext = decode(encdata, testMap8)
|
iv_ciphertext = decode(encdata, testMap8)
|
||||||
# pad IV so that we can substitute AES-CTR for GCM
|
# pad IV so that we can substitute AES-CTR for GCM
|
||||||
@ -1174,114 +553,8 @@ if iswindows:
|
|||||||
DB = {}
|
DB = {}
|
||||||
return DB
|
return DB
|
||||||
elif isosx:
|
elif isosx:
|
||||||
import copy
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
# interface to needed routines in openssl's libcrypto
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes import CDLL, byref, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, addressof, string_at, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
if libcrypto is None:
|
|
||||||
libcrypto = '/usr/lib/libcrypto.dylib'
|
|
||||||
try:
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
except Exception as e:
|
|
||||||
raise DrmException("libcrypto not found: " % e)
|
|
||||||
|
|
||||||
# From OpenSSL's crypto aes header
|
|
||||||
#
|
|
||||||
# AES_ENCRYPT 1
|
|
||||||
# AES_DECRYPT 0
|
|
||||||
# AES_MAXNR 14 (in bytes)
|
|
||||||
# AES_BLOCK_SIZE 16 (in bytes)
|
|
||||||
#
|
|
||||||
# struct aes_key_st {
|
|
||||||
# unsigned long rd_key[4 *(AES_MAXNR + 1)];
|
|
||||||
# int rounds;
|
|
||||||
# };
|
|
||||||
# typedef struct aes_key_st AES_KEY;
|
|
||||||
#
|
|
||||||
# int AES_set_decrypt_key(const unsigned char *userKey, const int bits, AES_KEY *key);
|
|
||||||
#
|
|
||||||
# note: the ivec string, and output buffer are both mutable
|
|
||||||
# void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
|
|
||||||
# const unsigned long length, const AES_KEY *key, unsigned char *ivec, const int enc);
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))), ('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
AES_cbc_encrypt = F(None, 'AES_cbc_encrypt',[c_char_p, c_char_p, c_ulong, AES_KEY_p, c_char_p,c_int])
|
|
||||||
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',[c_char_p, c_int, AES_KEY_p])
|
|
||||||
|
|
||||||
# From OpenSSL's Crypto evp/p5_crpt2.c
|
|
||||||
#
|
|
||||||
# int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen,
|
|
||||||
# const unsigned char *salt, int saltlen, int iter,
|
|
||||||
# int keylen, unsigned char *out);
|
|
||||||
|
|
||||||
PKCS5_PBKDF2_HMAC_SHA1 = F(c_int, 'PKCS5_PBKDF2_HMAC_SHA1',
|
|
||||||
[c_char_p, c_ulong, c_char_p, c_ulong, c_ulong, c_ulong, c_char_p])
|
|
||||||
|
|
||||||
class LibCrypto(object):
|
|
||||||
def __init__(self):
|
|
||||||
self._blocksize = 0
|
|
||||||
self._keyctx = None
|
|
||||||
self._iv = 0
|
|
||||||
|
|
||||||
def set_decrypt_key(self, userkey, iv):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise DrmException("AES improper key used")
|
|
||||||
return
|
|
||||||
keyctx = self._keyctx = AES_KEY()
|
|
||||||
self._iv = iv
|
|
||||||
self._userkey = userkey
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, keyctx)
|
|
||||||
if rv < 0:
|
|
||||||
raise DrmException("Failed to initialize AES key")
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
out = create_string_buffer(len(data))
|
|
||||||
mutable_iv = create_string_buffer(self._iv, len(self._iv))
|
|
||||||
keyctx = self._keyctx
|
|
||||||
rv = AES_cbc_encrypt(data, out, len(data), keyctx, mutable_iv, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise DrmException("AES decryption failed")
|
|
||||||
return out.raw
|
|
||||||
|
|
||||||
def keyivgen(self, passwd, salt, iter, keylen):
|
|
||||||
saltlen = len(salt)
|
|
||||||
passlen = len(passwd)
|
|
||||||
out = create_string_buffer(keylen)
|
|
||||||
rv = PKCS5_PBKDF2_HMAC_SHA1(passwd, passlen, salt, saltlen, iter, keylen, out)
|
|
||||||
return out.raw
|
|
||||||
return LibCrypto
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
LibCrypto = None
|
|
||||||
try:
|
|
||||||
LibCrypto = _load_crypto_libcrypto()
|
|
||||||
except (ImportError, DrmException):
|
|
||||||
pass
|
|
||||||
return LibCrypto
|
|
||||||
|
|
||||||
LibCrypto = _load_crypto()
|
|
||||||
|
|
||||||
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
# Various character maps used to decrypt books. Probably supposed to act as obfuscation
|
||||||
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
|
||||||
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
|
||||||
@ -1411,33 +684,13 @@ elif isosx:
|
|||||||
#print "ID Strings:\n",strings
|
#print "ID Strings:\n",strings
|
||||||
return strings
|
return strings
|
||||||
|
|
||||||
|
|
||||||
# unprotect the new header blob in .kinf2011
|
|
||||||
# used in Kindle for Mac Version >= 1.9.0
|
|
||||||
def UnprotectHeaderData(encryptedData):
|
|
||||||
passwdData = b'header_key_data'
|
|
||||||
salt = b'HEADER.2011'
|
|
||||||
iter = 0x80
|
|
||||||
keylen = 0x100
|
|
||||||
crp = LibCrypto()
|
|
||||||
key_iv = crp.keyivgen(passwdData, salt, iter, keylen)
|
|
||||||
key = key_iv[0:32]
|
|
||||||
iv = key_iv[32:48]
|
|
||||||
crp.set_decrypt_key(key,iv)
|
|
||||||
cleartext = crp.decrypt(encryptedData)
|
|
||||||
return cleartext
|
|
||||||
|
|
||||||
|
|
||||||
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
# implements an Pseudo Mac Version of Windows built-in Crypto routine
|
||||||
class CryptUnprotectData(object):
|
class CryptUnprotectData(object):
|
||||||
def __init__(self, entropy, IDString):
|
def __init__(self, entropy, IDString):
|
||||||
sp = GetUserName() + b'+@#$%+' + IDString
|
sp = GetUserName() + b'+@#$%+' + IDString
|
||||||
passwdData = encode(SHA256(sp),charMap2)
|
passwdData = encode(SHA256(sp),charMap2)
|
||||||
salt = entropy
|
salt = entropy
|
||||||
self.crp = LibCrypto()
|
key_iv = PBKDF2(passwdData, salt, count=0x800, dkLen=0x400)
|
||||||
iter = 0x800
|
|
||||||
keylen = 0x400
|
|
||||||
key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
|
|
||||||
self.key = key_iv[0:32]
|
self.key = key_iv[0:32]
|
||||||
self.iv = key_iv[32:48]
|
self.iv = key_iv[32:48]
|
||||||
self.crp.set_decrypt_key(self.key, self.iv)
|
self.crp.set_decrypt_key(self.key, self.iv)
|
||||||
@ -1575,7 +828,7 @@ elif isosx:
|
|||||||
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
salt = str(0x6d8 * int(build)).encode('utf-8') + guid
|
||||||
sp = GetUserName() + b'+@#$%+' + IDString
|
sp = GetUserName() + b'+@#$%+' + IDString
|
||||||
passwd = encode(SHA256(sp), charMap5)
|
passwd = encode(SHA256(sp), charMap5)
|
||||||
key = LibCrypto().keyivgen(passwd, salt, 10000, 0x400)[:32]
|
key = PBKDF2(passwd, salt, count=10000, dkLen=0x400)[:32]
|
||||||
|
|
||||||
#print ("salt",salt)
|
#print ("salt",salt)
|
||||||
#print ("sp",sp)
|
#print ("sp",sp)
|
||||||
@ -1647,8 +900,6 @@ elif isosx:
|
|||||||
cleartext = cud.decrypt(encryptedValue)
|
cleartext = cud.decrypt(encryptedValue)
|
||||||
|
|
||||||
elif version == 6:
|
elif version == 6:
|
||||||
from Crypto.Cipher import AES
|
|
||||||
from Crypto.Util import Counter
|
|
||||||
# decode using new testMap8 to get IV + ciphertext
|
# decode using new testMap8 to get IV + ciphertext
|
||||||
iv_ciphertext = decode(encdata, testMap8)
|
iv_ciphertext = decode(encdata, testMap8)
|
||||||
# pad IV so that we can substitute AES-CTR for GCM
|
# pad IV so that we can substitute AES-CTR for GCM
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__version__ = "1.0"
|
__version__ = "1.1"
|
||||||
|
|
||||||
# This is a python script. You need a Python interpreter to run it.
|
# This is a python script. You need a Python interpreter to run it.
|
||||||
# For example, ActiveState Python, which exists for windows.
|
# For example, ActiveState Python, which exists for windows.
|
||||||
@ -74,6 +74,7 @@ __version__ = "1.0"
|
|||||||
# 0.41 - Fixed potential unicode problem in command line calls
|
# 0.41 - Fixed potential unicode problem in command line calls
|
||||||
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
# 0.42 - Added GPL v3 licence. updated/removed some print statements
|
||||||
# 1.0 - Python 3 compatibility for calibre 5.0
|
# 1.0 - Python 3 compatibility for calibre 5.0
|
||||||
|
# 1.1 - Speed Python PC1 implementation up a little bit
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
@ -175,7 +176,7 @@ def PC1(key, src, decryption=True):
|
|||||||
wkey = []
|
wkey = []
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
wkey.append(key[i*2]<<8 | key[i*2+1])
|
wkey.append(key[i*2]<<8 | key[i*2+1])
|
||||||
dst = b''
|
dst = bytearray(len(src))
|
||||||
for i in range(len(src)):
|
for i in range(len(src)):
|
||||||
temp1 = 0;
|
temp1 = 0;
|
||||||
byteXorVal = 0;
|
byteXorVal = 0;
|
||||||
@ -194,8 +195,8 @@ def PC1(key, src, decryption=True):
|
|||||||
keyXorVal = curByte * 257;
|
keyXorVal = curByte * 257;
|
||||||
for j in range(8):
|
for j in range(8):
|
||||||
wkey[j] ^= keyXorVal;
|
wkey[j] ^= keyXorVal;
|
||||||
dst+=bytes([curByte])
|
dst[i] = curByte
|
||||||
return dst
|
return bytes(dst)
|
||||||
|
|
||||||
# accepts unicode returns unicode
|
# accepts unicode returns unicode
|
||||||
def checksumPid(s):
|
def checksumPid(s):
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
# implement just enough of des from openssl to make erdr2pml.py happy
|
|
||||||
|
|
||||||
def load_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_char, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
# typedef struct DES_ks
|
|
||||||
# {
|
|
||||||
# union
|
|
||||||
# {
|
|
||||||
# DES_cblock cblock;
|
|
||||||
# /* make sure things are correct size on machines with
|
|
||||||
# * 8 byte longs */
|
|
||||||
# DES_LONG deslong[2];
|
|
||||||
# } ks[16];
|
|
||||||
# } DES_key_schedule;
|
|
||||||
|
|
||||||
# just create a big enough place to hold everything
|
|
||||||
# it will have alignment of structure so we should be okay (16 byte aligned?)
|
|
||||||
class DES_KEY_SCHEDULE(Structure):
|
|
||||||
_fields_ = [('DES_cblock1', c_char * 16),
|
|
||||||
('DES_cblock2', c_char * 16),
|
|
||||||
('DES_cblock3', c_char * 16),
|
|
||||||
('DES_cblock4', c_char * 16),
|
|
||||||
('DES_cblock5', c_char * 16),
|
|
||||||
('DES_cblock6', c_char * 16),
|
|
||||||
('DES_cblock7', c_char * 16),
|
|
||||||
('DES_cblock8', c_char * 16),
|
|
||||||
('DES_cblock9', c_char * 16),
|
|
||||||
('DES_cblock10', c_char * 16),
|
|
||||||
('DES_cblock11', c_char * 16),
|
|
||||||
('DES_cblock12', c_char * 16),
|
|
||||||
('DES_cblock13', c_char * 16),
|
|
||||||
('DES_cblock14', c_char * 16),
|
|
||||||
('DES_cblock15', c_char * 16),
|
|
||||||
('DES_cblock16', c_char * 16)]
|
|
||||||
|
|
||||||
DES_KEY_SCHEDULE_p = POINTER(DES_KEY_SCHEDULE)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
DES_set_key = F(None, 'DES_set_key',[c_char_p, DES_KEY_SCHEDULE_p])
|
|
||||||
DES_ecb_encrypt = F(None, 'DES_ecb_encrypt',[c_char_p, c_char_p, DES_KEY_SCHEDULE_p, c_int])
|
|
||||||
|
|
||||||
|
|
||||||
class DES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
if len(key) != 8 :
|
|
||||||
raise Exception('DES improper key used')
|
|
||||||
return
|
|
||||||
self.key = key
|
|
||||||
self.keyschedule = DES_KEY_SCHEDULE()
|
|
||||||
DES_set_key(self.key, self.keyschedule)
|
|
||||||
def desdecrypt(self, data):
|
|
||||||
ob = create_string_buffer(len(data))
|
|
||||||
DES_ecb_encrypt(data, ob, self.keyschedule, 0)
|
|
||||||
return ob.raw
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not data:
|
|
||||||
return b''
|
|
||||||
i = 0
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = data[i:i+8]
|
|
||||||
processed_block = self.desdecrypt(block)
|
|
||||||
result.append(processed_block)
|
|
||||||
i += 8
|
|
||||||
return b''.join(result)
|
|
||||||
|
|
||||||
return DES
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
|
|
||||||
|
|
||||||
def load_pycrypto():
|
|
||||||
try :
|
|
||||||
from Crypto.Cipher import DES as _DES
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
class DES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
if len(key) != 8 :
|
|
||||||
raise ValueError('DES improper key used')
|
|
||||||
self.key = key
|
|
||||||
self._des = _DES.new(key,_DES.MODE_ECB)
|
|
||||||
def desdecrypt(self, data):
|
|
||||||
return self._des.decrypt(data)
|
|
||||||
def decrypt(self, data):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
i = 0
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = data[i:i+8]
|
|
||||||
processed_block = self.desdecrypt(block)
|
|
||||||
result.append(processed_block)
|
|
||||||
i += 8
|
|
||||||
return ''.join(result)
|
|
||||||
return DES
|
|
@ -1,220 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# vim:ts=4:sw=4:softtabstop=4:smarttab:expandtab
|
|
||||||
import sys
|
|
||||||
|
|
||||||
ECB = 0
|
|
||||||
CBC = 1
|
|
||||||
class Des(object):
|
|
||||||
__pc1 = [56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
|
|
||||||
9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
|
|
||||||
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
|
|
||||||
13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3]
|
|
||||||
__left_rotations = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
|
|
||||||
__pc2 = [13, 16, 10, 23, 0, 4,2, 27, 14, 5, 20, 9,
|
|
||||||
22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
|
|
||||||
40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
|
|
||||||
43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31]
|
|
||||||
__ip = [57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3,
|
|
||||||
61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7,
|
|
||||||
56, 48, 40, 32, 24, 16, 8, 0, 58, 50, 42, 34, 26, 18, 10, 2,
|
|
||||||
60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6]
|
|
||||||
__expansion_table = [31, 0, 1, 2, 3, 4, 3, 4, 5, 6, 7, 8,
|
|
||||||
7, 8, 9, 10, 11, 12,11, 12, 13, 14, 15, 16,
|
|
||||||
15, 16, 17, 18, 19, 20,19, 20, 21, 22, 23, 24,
|
|
||||||
23, 24, 25, 26, 27, 28,27, 28, 29, 30, 31, 0]
|
|
||||||
__sbox = [[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
|
|
||||||
0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
|
|
||||||
4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
|
|
||||||
15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13],
|
|
||||||
[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
|
|
||||||
3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
|
|
||||||
0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
|
|
||||||
13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9],
|
|
||||||
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
|
|
||||||
13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
|
|
||||||
13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
|
|
||||||
1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12],
|
|
||||||
[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
|
|
||||||
13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
|
|
||||||
10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
|
|
||||||
3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14],
|
|
||||||
[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
|
|
||||||
14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
|
|
||||||
4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
|
|
||||||
11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3],
|
|
||||||
[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
|
|
||||||
10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
|
|
||||||
9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
|
|
||||||
4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13],
|
|
||||||
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
|
|
||||||
13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
|
|
||||||
1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
|
|
||||||
6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12],
|
|
||||||
[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
|
|
||||||
1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
|
|
||||||
7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
|
|
||||||
2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11],]
|
|
||||||
__p = [15, 6, 19, 20, 28, 11,27, 16, 0, 14, 22, 25,
|
|
||||||
4, 17, 30, 9, 1, 7,23,13, 31, 26, 2, 8,18, 12, 29, 5, 21, 10,3, 24]
|
|
||||||
__fp = [39, 7, 47, 15, 55, 23, 63, 31,38, 6, 46, 14, 54, 22, 62, 30,
|
|
||||||
37, 5, 45, 13, 53, 21, 61, 29,36, 4, 44, 12, 52, 20, 60, 28,
|
|
||||||
35, 3, 43, 11, 51, 19, 59, 27,34, 2, 42, 10, 50, 18, 58, 26,
|
|
||||||
33, 1, 41, 9, 49, 17, 57, 25,32, 0, 40, 8, 48, 16, 56, 24]
|
|
||||||
# Type of crypting being done
|
|
||||||
ENCRYPT = 0x00
|
|
||||||
DECRYPT = 0x01
|
|
||||||
def __init__(self, key, mode=ECB, IV=None):
|
|
||||||
if len(key) != 8:
|
|
||||||
raise ValueError("Invalid DES key size. Key must be exactly 8 bytes long.")
|
|
||||||
self.block_size = 8
|
|
||||||
self.key_size = 8
|
|
||||||
self.__padding = ''
|
|
||||||
self.setMode(mode)
|
|
||||||
if IV:
|
|
||||||
self.setIV(IV)
|
|
||||||
self.L = []
|
|
||||||
self.R = []
|
|
||||||
self.Kn = [ [0] * 48 ] * 16 # 16 48-bit keys (K1 - K16)
|
|
||||||
self.final = []
|
|
||||||
self.setKey(key)
|
|
||||||
def getKey(self):
|
|
||||||
return self.__key
|
|
||||||
def setKey(self, key):
|
|
||||||
self.__key = key
|
|
||||||
self.__create_sub_keys()
|
|
||||||
def getMode(self):
|
|
||||||
return self.__mode
|
|
||||||
def setMode(self, mode):
|
|
||||||
self.__mode = mode
|
|
||||||
def getIV(self):
|
|
||||||
return self.__iv
|
|
||||||
def setIV(self, IV):
|
|
||||||
if not IV or len(IV) != self.block_size:
|
|
||||||
raise ValueError("Invalid Initial Value (IV), must be a multiple of " + str(self.block_size) + " bytes")
|
|
||||||
self.__iv = IV
|
|
||||||
def getPadding(self):
|
|
||||||
return self.__padding
|
|
||||||
def __String_to_BitList(self, data):
|
|
||||||
l = len(data) * 8
|
|
||||||
result = [0] * l
|
|
||||||
pos = 0
|
|
||||||
for c in data:
|
|
||||||
i = 7
|
|
||||||
ch = ord(c)
|
|
||||||
while i >= 0:
|
|
||||||
if ch & (1 << i) != 0:
|
|
||||||
result[pos] = 1
|
|
||||||
else:
|
|
||||||
result[pos] = 0
|
|
||||||
pos += 1
|
|
||||||
i -= 1
|
|
||||||
return result
|
|
||||||
def __BitList_to_String(self, data):
|
|
||||||
result = ''
|
|
||||||
pos = 0
|
|
||||||
c = 0
|
|
||||||
while pos < len(data):
|
|
||||||
c += data[pos] << (7 - (pos % 8))
|
|
||||||
if (pos % 8) == 7:
|
|
||||||
result += chr(c)
|
|
||||||
c = 0
|
|
||||||
pos += 1
|
|
||||||
return result
|
|
||||||
def __permutate(self, table, block):
|
|
||||||
return [block[x] for x in table]
|
|
||||||
def __create_sub_keys(self):
|
|
||||||
key = self.__permutate(Des.__pc1, self.__String_to_BitList(self.getKey()))
|
|
||||||
i = 0
|
|
||||||
self.L = key[:28]
|
|
||||||
self.R = key[28:]
|
|
||||||
while i < 16:
|
|
||||||
j = 0
|
|
||||||
while j < Des.__left_rotations[i]:
|
|
||||||
self.L.append(self.L[0])
|
|
||||||
del self.L[0]
|
|
||||||
self.R.append(self.R[0])
|
|
||||||
del self.R[0]
|
|
||||||
j += 1
|
|
||||||
self.Kn[i] = self.__permutate(Des.__pc2, self.L + self.R)
|
|
||||||
i += 1
|
|
||||||
def __des_crypt(self, block, crypt_type):
|
|
||||||
block = self.__permutate(Des.__ip, block)
|
|
||||||
self.L = block[:32]
|
|
||||||
self.R = block[32:]
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
iteration = 0
|
|
||||||
iteration_adjustment = 1
|
|
||||||
else:
|
|
||||||
iteration = 15
|
|
||||||
iteration_adjustment = -1
|
|
||||||
i = 0
|
|
||||||
while i < 16:
|
|
||||||
tempR = self.R[:]
|
|
||||||
self.R = self.__permutate(Des.__expansion_table, self.R)
|
|
||||||
self.R = [x ^ y for x,y in zip(self.R, self.Kn[iteration])]
|
|
||||||
B = [self.R[:6], self.R[6:12], self.R[12:18], self.R[18:24], self.R[24:30], self.R[30:36], self.R[36:42], self.R[42:]]
|
|
||||||
j = 0
|
|
||||||
Bn = [0] * 32
|
|
||||||
pos = 0
|
|
||||||
while j < 8:
|
|
||||||
m = (B[j][0] << 1) + B[j][5]
|
|
||||||
n = (B[j][1] << 3) + (B[j][2] << 2) + (B[j][3] << 1) + B[j][4]
|
|
||||||
v = Des.__sbox[j][(m << 4) + n]
|
|
||||||
Bn[pos] = (v & 8) >> 3
|
|
||||||
Bn[pos + 1] = (v & 4) >> 2
|
|
||||||
Bn[pos + 2] = (v & 2) >> 1
|
|
||||||
Bn[pos + 3] = v & 1
|
|
||||||
pos += 4
|
|
||||||
j += 1
|
|
||||||
self.R = self.__permutate(Des.__p, Bn)
|
|
||||||
self.R = [x ^ y for x, y in zip(self.R, self.L)]
|
|
||||||
self.L = tempR
|
|
||||||
i += 1
|
|
||||||
iteration += iteration_adjustment
|
|
||||||
self.final = self.__permutate(Des.__fp, self.R + self.L)
|
|
||||||
return self.final
|
|
||||||
def crypt(self, data, crypt_type):
|
|
||||||
if not data:
|
|
||||||
return ''
|
|
||||||
if len(data) % self.block_size != 0:
|
|
||||||
if crypt_type == Des.DECRYPT: # Decryption must work on 8 byte blocks
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n.")
|
|
||||||
if not self.getPadding():
|
|
||||||
raise ValueError("Invalid data length, data must be a multiple of " + str(self.block_size) + " bytes\n. Try setting the optional padding character")
|
|
||||||
else:
|
|
||||||
data += (self.block_size - (len(data) % self.block_size)) * self.getPadding()
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if self.getIV():
|
|
||||||
iv = self.__String_to_BitList(self.getIV())
|
|
||||||
else:
|
|
||||||
raise ValueError("For CBC mode, you must supply the Initial Value (IV) for ciphering")
|
|
||||||
i = 0
|
|
||||||
dict = {}
|
|
||||||
result = []
|
|
||||||
while i < len(data):
|
|
||||||
block = self.__String_to_BitList(data[i:i+8])
|
|
||||||
if self.getMode() == CBC:
|
|
||||||
if crypt_type == Des.ENCRYPT:
|
|
||||||
block = [x ^ y for x, y in zip(block, iv)]
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
if crypt_type == Des.DECRYPT:
|
|
||||||
processed_block = [x ^ y for x, y in zip(processed_block, iv)]
|
|
||||||
iv = block
|
|
||||||
else:
|
|
||||||
iv = processed_block
|
|
||||||
else:
|
|
||||||
processed_block = self.__des_crypt(block, crypt_type)
|
|
||||||
result.append(self.__BitList_to_String(processed_block))
|
|
||||||
i += 8
|
|
||||||
if crypt_type == Des.DECRYPT and self.getPadding():
|
|
||||||
s = result[-1]
|
|
||||||
while s[-1] == self.getPadding():
|
|
||||||
s = s[:-1]
|
|
||||||
result[-1] = s
|
|
||||||
return ''.join(result)
|
|
||||||
def encrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.ENCRYPT)
|
|
||||||
def decrypt(self, data, pad=''):
|
|
||||||
self.__padding = pad
|
|
||||||
return self.crypt(data, Des.DECRYPT)
|
|
@ -1,6 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Version 10.0.1 February 2022
|
||||||
|
# Remove OpenSSL support to only support PyCryptodome; clean up the code.
|
||||||
|
#
|
||||||
# Version 10.0.0 November 2021
|
# Version 10.0.0 November 2021
|
||||||
# Merge https://github.com/apprenticeharper/DeDRM_tools/pull/1691 to fix
|
# Merge https://github.com/apprenticeharper/DeDRM_tools/pull/1691 to fix
|
||||||
# key fetch issues on some machines.
|
# key fetch issues on some machines.
|
||||||
@ -162,7 +165,7 @@
|
|||||||
"""Manage all Kobo books, either encrypted or DRM-free."""
|
"""Manage all Kobo books, either encrypted or DRM-free."""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
__version__ = '4.0.0'
|
__version__ = '10.0.1'
|
||||||
__about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
|
__about__ = "Obok v{0}\nCopyright © 2012-2020 Physisticated et al.".format(__version__)
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -180,6 +183,13 @@ import shutil
|
|||||||
import argparse
|
import argparse
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
from Cryptodome.Cipher import AES
|
||||||
|
from Cryptodome.Util.Padding import unpad
|
||||||
|
except ImportError:
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import unpad
|
||||||
|
|
||||||
can_parse_xml = True
|
can_parse_xml = True
|
||||||
try:
|
try:
|
||||||
from xml.etree import ElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
@ -194,88 +204,6 @@ KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
|
|||||||
class ENCRYPTIONError(Exception):
|
class ENCRYPTIONError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _load_crypto_libcrypto():
|
|
||||||
from ctypes import CDLL, POINTER, c_void_p, c_char_p, c_int, c_long, \
|
|
||||||
Structure, c_ulong, create_string_buffer, cast
|
|
||||||
from ctypes.util import find_library
|
|
||||||
|
|
||||||
if sys.platform.startswith('win'):
|
|
||||||
libcrypto = find_library('libeay32')
|
|
||||||
else:
|
|
||||||
libcrypto = find_library('crypto')
|
|
||||||
|
|
||||||
if libcrypto is None:
|
|
||||||
raise ENCRYPTIONError('libcrypto not found')
|
|
||||||
libcrypto = CDLL(libcrypto)
|
|
||||||
|
|
||||||
AES_MAXNR = 14
|
|
||||||
|
|
||||||
c_char_pp = POINTER(c_char_p)
|
|
||||||
c_int_p = POINTER(c_int)
|
|
||||||
|
|
||||||
class AES_KEY(Structure):
|
|
||||||
_fields_ = [('rd_key', c_long * (4 * (AES_MAXNR + 1))),
|
|
||||||
('rounds', c_int)]
|
|
||||||
AES_KEY_p = POINTER(AES_KEY)
|
|
||||||
|
|
||||||
def F(restype, name, argtypes):
|
|
||||||
func = getattr(libcrypto, name)
|
|
||||||
func.restype = restype
|
|
||||||
func.argtypes = argtypes
|
|
||||||
return func
|
|
||||||
|
|
||||||
AES_set_decrypt_key = F(c_int, 'AES_set_decrypt_key',
|
|
||||||
[c_char_p, c_int, AES_KEY_p])
|
|
||||||
AES_ecb_encrypt = F(None, 'AES_ecb_encrypt',
|
|
||||||
[c_char_p, c_char_p, AES_KEY_p, c_int])
|
|
||||||
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, userkey):
|
|
||||||
self._blocksize = len(userkey)
|
|
||||||
if (self._blocksize != 16) and (self._blocksize != 24) and (self._blocksize != 32) :
|
|
||||||
raise ENCRYPTIONError(_('AES improper key used'))
|
|
||||||
return
|
|
||||||
key = self._key = AES_KEY()
|
|
||||||
rv = AES_set_decrypt_key(userkey, len(userkey) * 8, key)
|
|
||||||
if rv < 0:
|
|
||||||
raise ENCRYPTIONError(_('Failed to initialize AES key'))
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
clear = b''
|
|
||||||
for i in range(0, len(data), 16):
|
|
||||||
out = create_string_buffer(16)
|
|
||||||
rv = AES_ecb_encrypt(data[i:i+16], out, self._key, 0)
|
|
||||||
if rv == 0:
|
|
||||||
raise ENCRYPTIONError(_('AES decryption failed'))
|
|
||||||
clear += out.raw
|
|
||||||
return clear
|
|
||||||
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto_pycrypto():
|
|
||||||
from Crypto.Cipher import AES as _AES
|
|
||||||
class AES(object):
|
|
||||||
def __init__(self, key):
|
|
||||||
self._aes = _AES.new(key, _AES.MODE_ECB)
|
|
||||||
|
|
||||||
def decrypt(self, data):
|
|
||||||
return self._aes.decrypt(data)
|
|
||||||
|
|
||||||
return AES
|
|
||||||
|
|
||||||
def _load_crypto():
|
|
||||||
AES = None
|
|
||||||
cryptolist = (_load_crypto_pycrypto, _load_crypto_libcrypto)
|
|
||||||
for loader in cryptolist:
|
|
||||||
try:
|
|
||||||
AES = loader()
|
|
||||||
break
|
|
||||||
except (ImportError, ENCRYPTIONError):
|
|
||||||
pass
|
|
||||||
return AES
|
|
||||||
|
|
||||||
AES = _load_crypto()
|
|
||||||
|
|
||||||
# Wrap a stream so that output gets flushed immediately
|
# Wrap a stream so that output gets flushed immediately
|
||||||
# and also make sure that any unicode strings get
|
# and also make sure that any unicode strings get
|
||||||
# encoded using "replace" before writing them.
|
# encoded using "replace" before writing them.
|
||||||
@ -630,11 +558,9 @@ class KoboFile(object):
|
|||||||
file page key. The caller must determine if the decrypted
|
file page key. The caller must determine if the decrypted
|
||||||
data is correct."""
|
data is correct."""
|
||||||
# The userkey decrypts the page key (self.key)
|
# The userkey decrypts the page key (self.key)
|
||||||
keyenc = AES(userkey)
|
decryptedkey = AES.new(userkey, AES.MODE_ECB).decrypt(self.key)
|
||||||
decryptedkey = keyenc.decrypt(self.key)
|
# The decrypted page key decrypts the content. Padding is PKCS#7
|
||||||
# The decrypted page key decrypts the content
|
return unpad(AES.new(decryptedkey, AES.MODE_ECB).decrypt(contents), 16)
|
||||||
pageenc = AES(decryptedkey)
|
|
||||||
return self.__removeaespadding(pageenc.decrypt(contents))
|
|
||||||
|
|
||||||
def check (self, contents):
|
def check (self, contents):
|
||||||
"""
|
"""
|
||||||
@ -704,23 +630,6 @@ class KoboFile(object):
|
|||||||
raise ValueError()
|
raise ValueError()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __removeaespadding (self, contents):
|
|
||||||
"""
|
|
||||||
Remove the trailing padding, using what appears to be the CMS
|
|
||||||
algorithm from RFC 5652 6.3"""
|
|
||||||
lastchar = binascii.b2a_hex(contents[-1:])
|
|
||||||
strlen = int(lastchar, 16)
|
|
||||||
padding = strlen
|
|
||||||
if strlen == 1:
|
|
||||||
return contents[:-1]
|
|
||||||
if strlen < 16:
|
|
||||||
for i in range(strlen):
|
|
||||||
testchar = binascii.b2a_hex(contents[-strlen:-(strlen-1)])
|
|
||||||
if testchar != lastchar:
|
|
||||||
padding = 0
|
|
||||||
if padding > 0:
|
|
||||||
contents = contents[:-padding]
|
|
||||||
return contents
|
|
||||||
|
|
||||||
def decrypt_book(book, lib):
|
def decrypt_book(book, lib):
|
||||||
print("Converting {0}".format(book.title))
|
print("Converting {0}".format(book.title))
|
||||||
|
Loading…
Reference in New Issue
Block a user