]> xmof Git - DeDRM.git/commitdiff
Remove OpenSSL support; only support PyCryptodome
authora980e066a01 <100724039+a980e066a01@users.noreply.github.com>
Tue, 22 Feb 2022 23:16:03 +0000 (23:16 +0000)
committernoDRM <no_drm123@protonmail.com>
Fri, 18 Mar 2022 15:45:39 +0000 (15:45 +0000)
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.

16 files changed:
DeDRM_plugin/adobekey.py
DeDRM_plugin/adobekey_get_passhash.py
DeDRM_plugin/androidkindlekey.py
DeDRM_plugin/erdr2pml.py
DeDRM_plugin/ignoblekeyAndroid.py
DeDRM_plugin/ignoblekeyGenPassHash.py
DeDRM_plugin/ignoblekeyWindowsStore.py
DeDRM_plugin/ineptepub.py
DeDRM_plugin/ineptpdf.py
DeDRM_plugin/ion.py
DeDRM_plugin/kindlekey.py
DeDRM_plugin/mobidedrm.py
DeDRM_plugin/openssl_des.py [deleted file]
DeDRM_plugin/pycrypto_des.py [deleted file]
DeDRM_plugin/python_des.py [deleted file]
Obok_plugin/obok/obok.py

index 6155cff3030e8651a535cc3d3be5acc9e7bfb89f..36c18decf73800626c23d26ed3280e8d4c669457 100644 (file)
@@ -1,8 +1,8 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 
-# adobekey.pyw, version 7.1
-# Copyright © 2009-2021 i♥cabbages, Apprentice Harper et al.
+# adobekey.pyw, version 7.4
+# Copyright © 2009-2022 i♥cabbages, Apprentice Harper et al.
 
 # Released under the terms of the GNU General Public Licence, version 3
 # <http://www.gnu.org/licenses/>
@@ -32,6 +32,7 @@
 #   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.3 - Fix OpenSSL in Wine
+#   7.4 - Remove OpenSSL support to only support PyCryptodome
 
 """
 Retrieve Adobe ADEPT user key.
@@ -125,91 +126,12 @@ if iswindows:
     except ImportError:
         import _winreg as winreg
 
-    def get_fake_windows_libcrypto_path():
-        # There seems to be a bug in Wine where a `find_library('libcrypto-1_1')` 
-        # will not return the path to the libcrypto-1_1.dll file.
-        # So if we're on Windows, and we didn't find the libcrypto the normal way,
-        # lets try a hack-y workaround. It's already over anyways at this 
-        # point, can't really make it worse. 
-        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()
-
+    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
 
     DEVICE_KEY_PATH = r'Software\Adobe\Adept\Device'
     PRIVATE_LICENCE_KEY_PATH = r'Software\Adobe\Adept\Activation'
@@ -402,8 +324,6 @@ if iswindows:
     CryptUnprotectData = CryptUnprotectData()
 
     def adeptkeys():
-        if AES is None:
-            raise ADEPTError("PyCrypto or OpenSSL must be installed")
         root = GetSystemDirectory().split('\\')[0] + '\\'
         serial = GetVolumeSerialNumber(root)
         vendor = cpuid0()
@@ -416,7 +336,7 @@ if iswindows:
         try:
             regkey = winreg.OpenKey(cuser, DEVICE_KEY_PATH)
             device = winreg.QueryValueEx(regkey, 'key')[0]
-        except WindowsError, FileNotFoundError:
+        except (WindowsError, FileNotFoundError):
             raise ADEPTError("Adobe Digital Editions not activated")
         keykey = CryptUnprotectData(device, entropy)
         userkey = None
@@ -424,7 +344,7 @@ if iswindows:
         names = []
         try:
             plkroot = winreg.OpenKey(cuser, PRIVATE_LICENCE_KEY_PATH)
-        except WindowsError, FileNotFoundError:
+        except (WindowsError, FileNotFoundError):
             raise ADEPTError("Could not locate ADE activation")
 
         i = -1
@@ -443,7 +363,7 @@ if iswindows:
             for j in range(0, 16):
                 try:
                     plkkey = winreg.OpenKey(plkparent, "%04d" % (j,))
-                except WindowsError, FileNotFoundError:
+                except (WindowsError, FileNotFoundError):
                     break
                 ktype = winreg.QueryValueEx(plkkey, None)[0]
                 if ktype == 'user':
@@ -461,10 +381,7 @@ if iswindows:
                         pass
                 if ktype == 'privateLicenseKey':
                     userkey = winreg.QueryValueEx(plkkey, 'value')[0]
-                    userkey = b64decode(userkey)
-                    aes = AES(keykey)
-                    userkey = aes.decrypt(userkey)
-                    userkey = userkey[26:-ord(userkey[-1:])]
+                    userkey = unpad(AES.new(keykey, AES.MODE_CBC, b'\x00'*16).decrypt(b64decode(userkey)), 16)[26:]
                     # print ("found " + uuid_name + " key: " + str(userkey))
                     keys.append(userkey)
 
index 05f4c32542a48e7cc681ef025518449245bd63e9..72b7cd5c743a5c1b10b3f1b4637af30849838810 100644 (file)
@@ -23,20 +23,13 @@ import sys, os, time
 import base64, hashlib
 try: 
     from Cryptodome.Cipher import AES
-except:
+    from Cryptodome.Util.Padding import unpad
+except ImportError:
     from Crypto.Cipher import AES
+    from Crypto.Util.Padding import unpad
 
 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:
     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]
 
     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")
 
 
index da34c1d3a0938a1b988bb2cc1b57047423ae40bf..6b152f3ead6bc3e52afbd0995eb62b44b0f890f1 100755 (executable)
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 # androidkindlekey.py
-# Copyright © 2010-20 by Thom, Apprentice Harper et al.
+# Copyright © 2010-22 by Thom, Apprentice Harper et al.
 
 # Revision history:
 #  1.0   - AmazonSecureStorage.xml decryption to serial number
 #  1.4   - Fix some problems identified by Aldo Bleeker
 #  1.5   - Fix another problem identified by Aldo Bleeker
 #  2.0   - Python 3 compatibility
+#  2.1   - Remove OpenSSL support; only support PyCryptodome
 
 """
 Retrieve Kindle for Android Serial Number.
 """
 
 __license__ = 'GPL v3'
-__version__ = '2.0'
+__version__ = '2.1'
 
 import os
 import sys
@@ -33,6 +34,13 @@ from hashlib import md5
 from io import BytesIO
 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
 
 # Wrap a stream so that output gets flushed immediately
@@ -115,24 +123,16 @@ class AndroidObfuscation(object):
 
     key = a2b_hex('0176e04c9408b1702d90be333fd53523')
 
+    def _get_cipher(self):
+        return AES.new(self.key, AES.MODE_ECB)
+
     def encrypt(self, plaintext):
-        cipher = self._get_cipher()
-        padding = len(self.key) - len(plaintext) % len(self.key)
-        plaintext += chr(padding) * padding
-        return b2a_hex(cipher.encrypt(plaintext.encode('utf-8')))
+        pt = pad(plaintext.encode('utf-8'), 16)
+        return b2a_hex(self._get_cipher().encrypt(pt))
 
     def decrypt(self, ciphertext):
-        cipher = self._get_cipher()
-        plaintext = cipher.decrypt(a2b_hex(ciphertext))
-        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())
+        ct = a2b_hex(ciphertext)
+        return unpad(self._get_cipher().decrypt(ct), 16)
 
 class AndroidObfuscationV2(AndroidObfuscation):
     '''AndroidObfuscationV2
@@ -149,12 +149,7 @@ class AndroidObfuscationV2(AndroidObfuscation):
         self.iv = key[8:16]
 
     def _get_cipher(self):
-        try :
-            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)
+        return DES.new(self.key, DES.MODE_CBC, self.iv)
 
 def parse_preference(path):
     ''' parse android's shared preference xml '''
index 26c947d426ee0ae488908f84187f40129bfdef7e..6a20fdb9cf6c7e9ab03f087763c11b5181ba1f8f 100755 (executable)
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 # 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
 #
 #  0.23 - moved unicode_argv call inside main for Windows DeDRM compatibility
 #  1.00 - Added Python 3 compatibility for calibre 5.0
 #  1.01 - Bugfixes for standalone version.
+#  1.02 - Remove OpenSSL support; only use PyCryptodome
 
-__version__='1.00'
+__version__='1.02'
 
 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@@
 
@@ -136,44 +142,6 @@ def unicode_argv():
         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]
 
-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 logging
 
@@ -253,7 +221,7 @@ class EreaderProcessor(object):
             raise ValueError('incorrect eReader version %d (error 1)' % version)
         data = self.section_reader(1)
         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:]))
         if cookie_shuf < 3 or cookie_shuf > 0x14 or cookie_size < 0xf0 or cookie_size > 0x200:
             raise ValueError('incorrect eReader version (error 2)')
@@ -317,7 +285,7 @@ class EreaderProcessor(object):
         if (self.flags & reqd_flags) != reqd_flags:
             print("Flags: 0x%X" % self.flags)
             raise ValueError('incompatible eReader file')
-        des = Des(fixKey(user_key))
+        des = DES.new(fixKey(user_key), DES.MODE_ECB)
         if version == 259:
             if drm_sub_version != 7:
                 raise ValueError('incorrect eReader version %d (error 3)' % drm_sub_version)
@@ -393,7 +361,7 @@ class EreaderProcessor(object):
     #     return bkinfo
 
     def getText(self):
-        des = Des(fixKey(self.content_key))
+        des = DES.new(fixKey(self.content_key), DES.MODE_ECB)
         r = b''
         for i in range(self.num_text_pages):
             logging.debug('get page %d', i)
@@ -406,7 +374,7 @@ class EreaderProcessor(object):
             sect = self.section_reader(self.first_footnote_page)
             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
-            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):
                 logging.debug('get footnotepage %d', i)
                 id_len = ord(fnote_ids[2])
@@ -430,7 +398,7 @@ class EreaderProcessor(object):
             sect = self.section_reader(self.first_sidebar_page)
             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
-            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):
                 id_len = ord(sbar_ids[2])
                 id = sbar_ids[3:3+id_len]
index 2b3f0ecfe5492f5b2737f069509e025fd9e57467..5fcbd6af1f68cbd4c832864692bd54af511a6abd 100644 (file)
@@ -10,22 +10,16 @@ import os
 import base64
 try: 
     from Cryptodome.Cipher import AES
-except:
+    from Cryptodome.Util.Padding import unpad
+except ImportError:
     from Crypto.Cipher import AES
+    from Crypto.Util.Padding import unpad
 import hashlib
 from lxml import etree
 
 
 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):
     
@@ -53,7 +47,7 @@ def dump_keys(path_to_adobe_folder):
 
     for pass_hash in activation_xml.findall(".//{http://ns.adobe.com/adept}passHash"):
         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"))
         #print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
 
index cb6d208a39b1b92c48a1f4ab9b945ec6260d1c41..e1cdba05f8b647df37968fdb0f8ce1512d0c1864 100644 (file)
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 # 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
 # <http://www.gnu.org/licenses/>
 #   2.7 - Work if TkInter is missing
 #   2.8 - Fix bug in stand-alone use (import tkFileDialog)
 #   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.
 """
 
 __license__ = 'GPL v3'
-__version__ = "3.0"
+__version__ = "3.1"
 
 import sys
 import os
 import hashlib
 import base64
 
+try:
+    from Cryptodome.Cipher import AES
+except ImportError:
+    from Crypto.Cipher import AES
+
 # Wrap a stream so that output gets flushed immediately
 # and also make sure that any unicode strings get
 # encoded using "replace" before writing them.
@@ -114,87 +120,6 @@ def unicode_argv():
 class IGNOBLEError(Exception):
     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):
     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]
     ccn_sha = hashlib.sha1(ccn).digest()[:16]
     both_sha = hashlib.sha1(name + ccn).digest()
-    aes = AES(ccn_sha, name_sha)
-    crypt = aes.encrypt(both_sha + (b'\x0c' * 0x0c))
+    crypt = AES.new(ccn_sha, AES.MODE_CBC, name_sha).encrypt(both_sha + (b'\x0c' * 0x0c))
     userkey = hashlib.sha1(crypt).digest()
     return base64.b64encode(userkey)
 
@@ -226,11 +150,6 @@ def cli_main():
     sys.stderr=SafeUnbuffered(sys.stderr)
     argv=unicode_argv()
     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:
         print("usage: {0} <Name> <CC#> <keyfileout.b64>".format(progname))
         return 1
@@ -316,13 +235,6 @@ def gui_main():
             self.status['text'] = "Keyfile successfully generated"
 
     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.resizable(True, False)
     root.minsize(300, 0)
index 919d2e6717166d2fd2afa92aa13b875f7fe01085..f22d64db887e77a5bab911f47f3c5a52cf0b5cc2 100644 (file)
@@ -15,8 +15,10 @@ import apsw
 import base64
 try: 
     from Cryptodome.Cipher import AES
+    from Cryptodome.Util.Padding import unpad
 except:
     from Crypto.Cipher import AES
+    from Crypto.Util.Padding import unpad
 import hashlib
 from lxml import etree
 
@@ -24,15 +26,6 @@ from lxml import etree
 NOOK_DATA_FOLDER = "%LOCALAPPDATA%\\Packages\\BarnesNoble.Nook_ahnzqzva31enc\\LocalState"
 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):
     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"):
         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")))
         if print_result:
             print("Nook ccHash is %s" % (base64.b64encode(cc_hash).decode("ascii")))
index 7f343575a14df995c239d1d6fb936959686232e6..5fc6d4b0223e6cde4bf8d5962037d302a751205f 100644 (file)
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 # 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
 # <http://www.gnu.org/licenses/>
 #   6.6 - Import tkFileDialog, don't assume something else will import it.
 #   7.0 - Add Python 3 compatibility for calibre 5.0
 #   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.
 """
 
 __license__ = 'GPL v3'
-__version__ = "7.1"
+__version__ = "7.2"
 
 import sys
 import os
@@ -49,6 +50,15 @@ from zipfile import ZipInfo, ZipFile, ZIP_STORED, ZIP_DEFLATED
 from contextlib import closing
 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
 # and also make sure that any unicode strings get
 # encoded using "replace" before writing them.
@@ -120,234 +130,6 @@ class ADEPTError(Exception):
 class ADEPTNewVersionError(Exception):
     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')
 NSMAP = {'adept': 'http://ns.adobe.com/adept',
          'enc': 'http://www.w3.org/2001/04/xmlenc#'}
@@ -355,7 +137,7 @@ NSMAP = {'adept': 'http://ns.adobe.com/adept',
 class Decryptor(object):
     def __init__(self, bookkey, encryption):
         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)
         self._encrypted = encrypted = set()
         self._otherData = otherData = set()
@@ -373,11 +155,11 @@ class Decryptor(object):
                     path = path.encode('utf-8')
                     encrypted.add(path)
                     json_elements_to_remove.add(elem.getparent().getparent())
-                else: 
+                else:
                     path = path.encode('utf-8')
                     otherData.add(path)
                     self._has_remaining_xml = True
-        
+
         for elem in json_elements_to_remove:
             elem.getparent().remove(elem)
 
@@ -398,8 +180,8 @@ class Decryptor(object):
         except:
             # possibly not compressed by zip - just return bytes
             return bytes
-        return decompressed_bytes 
-    
+        return decompressed_bytes
+
     def decrypt(self, path, data):
         if path.encode('utf-8') in self._encrypted:
             data = self._aes.decrypt(data)[16:]
@@ -446,49 +228,26 @@ def isPassHashBook(inpath):
                 return True
         except:
             pass
-        
+
     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
 # first try without having to loop through all possible keys.
-def adeptGetUserUUID(inpath): 
+def adeptGetUserUUID(inpath):
     with closing(ZipFile(open(inpath, 'rb'))) as inf:
         try:
             rights = etree.fromstring(inf.read('META-INF/rights.xml'))
             adept = lambda tag: '{%s}%s' % (NSMAP['adept'], tag)
             expr = './/%s' % (adept('user'),)
             user_uuid = ''.join(rights.findtext(expr))
-            if user_uuid[:9] != "urn:uuid:": 
+            if user_uuid[:9] != "urn:uuid:":
                 return None
             return user_uuid[9:]
         except:
             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):
-    if AES is None:
-        raise ADEPTError("PyCrypto or OpenSSL must be installed.")
-
     with closing(ZipFile(open(inpath, 'rb'))) as inf:
         namelist = inf.namelist()
         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("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")
-            
+
             if len(bookkey) == 172:
                 print("{0:s} is a secure Adobe Adept ePub.".format(os.path.basename(inpath)))
             elif len(bookkey) == 64:
@@ -519,28 +278,21 @@ def decryptBook(userkey, inpath, outpath):
 
             if len(bookkey) != 64:
                 # Normal Adobe ADEPT
-                rsa = RSA(userkey)
-                bookkey = rsa.decrypt(base64.b64decode(bookkey.encode('ascii')))
-
-                # Verify key:
-                if len(bookkey) > 16:
-                    # Padded as per RSAES-PKCS1-v1_5
-                    if verify_book_key(bookkey):
-                        bookkey = bookkey[-16:]
-                    else:
-                        print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
-                        return 2
-            else: 
+                rsakey = RSA.import_key(userkey) # parses the ASN1 structure
+                bookkey = base64.b64decode(bookkey)
+                try:
+                    bookkey = PKCS1_v1_5.new(rsakey).decrypt(bookkey, None) # automatically unpads
+                except ValueError:
+                    bookkey = None
+
+                if bookkey is None:
+                    print("Could not decrypt {0:s}. Wrong key".format(os.path.basename(inpath)))
+                    return 2
+            else:
                 # Adobe PassHash / B&N
                 key = base64.b64decode(userkey)[:16]
-                aes = AES(key)
-                bookkey = aes.decrypt(base64.b64decode(bookkey))
-                if type(bookkey[-1]) != int:
-                    pad = ord(bookkey[-1])
-                else:
-                    pad = bookkey[-1]
-                
-                bookkey = bookkey[:-pad]
+                bookkey = base64.b64decode(bookkey)
+                bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
 
                 if len(bookkey) > 16:
                     bookkey = bookkey[-16:]
index 9bbf09211fdcb20f7fda02ca2c2aed1ceebbd5ad..2364c12ae5b18ecd3885dac7196d26c637a4f9db 100755 (executable)
@@ -3,7 +3,7 @@
 
 # ineptpdf.py
 # 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
 # <http://www.gnu.org/licenses/>
 #   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.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.
 """
 
 __license__ = 'GPL v3'
-__version__ = "9.1.0"
+__version__ = "9.1.1"
 
 import codecs
+import hashlib
 import sys
 import os
 import re
 import zlib
 import struct
-import hashlib
 from io import BytesIO
 from decimal import Decimal
 import itertools
 import xml.etree.ElementTree as etree
 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
 # and also make sure that any unicode strings get
 # encoded using "replace" before writing them.
@@ -140,313 +150,8 @@ class ADEPTInvalidPasswordError(Exception):
 class ADEPTNewVersionError(Exception):
     pass
 
-
-import hashlib
-
 def SHA256(message):
-    ctx = hashlib.sha256()
-    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()
-
-
-
+    return hashlib.sha256(message).digest()
 
 # Do we generate cross reference streams on output?
 # 0 = never
@@ -668,7 +373,7 @@ class PSBaseParser(object):
         if isinstance(s[j], str):
             # Python 2
             c = s[j]
-        else: 
+        else:
             # Python 3
             c = bytes([s[j]])
         self.tokenstart = self.bufpos+j
@@ -1593,14 +1298,14 @@ class PDFDocument(object):
 
     def check_user_password(self, password, docid, param):
         V = int_value(param.get('V', 0))
-        if V < 5: 
+        if V < 5:
             return self.check_user_password_V4(password, docid, param)
         else:
             return self.check_user_password_V5(password, param)
 
     def check_owner_password(self, password, docid, param):
         V = int_value(param.get('V', 0))
-        if V < 5: 
+        if V < 5:
             return self.check_owner_password_V4(password, docid, param)
         else:
             return self.check_owner_password_V5(password, param)
@@ -1664,7 +1369,7 @@ class PDFDocument(object):
             return plaintext
         else:
             aes = AES.new(key, AES.MODE_CBC, iv, False)
-            new_data = bytes(data * repetitions)           
+            new_data = bytes(data * repetitions)
             crypt = aes.encrypt(new_data)
             return crypt
 
@@ -1674,7 +1379,7 @@ class PDFDocument(object):
         K = SHA256(password + salt + userdata)
         if R < 6:
             return K
-        elif R == 6: 
+        elif R == 6:
             round_number = 0
             done = False
             while (not done):
@@ -1684,24 +1389,7 @@ class PDFDocument(object):
                     raise Exception("K1 < 32 ...")
                 #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_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()
+                K = (hashlib.sha256, hashlib.sha384, hashlib.sha512)[sum(E) % 3](E).digest()
 
                 if round_number >= 64:
                     ch = int.from_bytes(E[-1:], "big", signed=False)
@@ -1720,7 +1408,7 @@ class PDFDocument(object):
         V = int_value(param.get('V', 0))
         if V >= 5:
             raise Exception("compute_O_rc4_key not possible with V>= 5")
-    
+
         R = int_value(param.get('R', 0))
 
         length = int_value(param.get('Length', 40)) # Key length (bits)
@@ -1730,10 +1418,10 @@ class PDFDocument(object):
             for _ in range(50):
                 hash = hashlib.md5(hash.digest()[:length//8])
         hash = hash.digest()[:length//8]
-        
+
         # "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 ...
         x = ARC4.new(hash).decrypt(Odata) # 4
         if R >= 3:
@@ -1741,25 +1429,25 @@ class PDFDocument(object):
                 k = b''.join(bytes([c ^ i]) for c in hash )
                 x = ARC4.new(k).decrypt(x)
 
-        
+
         # "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.
-        # 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
         # as it is, as that function would be adding padding anyways.
         # This trick only works with V4 and lower.
-        
+
         enc_key = self.check_user_password(x, docid, param)
         if enc_key is not None:
             return enc_key
 
         return False
 
-        
 
-    
+
+
     def check_user_password_V4(self, password, docid, param):
 
         V = int_value(param.get('V', 0))
@@ -1803,10 +1491,10 @@ class PDFDocument(object):
             is_authenticated = (u1 == U)
         else:
             is_authenticated = (u1[:16] == U[:16])
-        
+
         if is_authenticated:
             return key
-        
+
         return None
 
     def initialize_standard(self, password, docid, param):
@@ -1842,7 +1530,7 @@ class PDFDocument(object):
                     self.decrypt_key = retval
 
         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'])
@@ -1868,8 +1556,8 @@ class PDFDocument(object):
         set_decipher = False
 
         if V >= 4:
-            # Check if we need new genkey_v4 - only if we're using AES. 
-            try: 
+            # Check if we need new genkey_v4 - only if we're using AES.
+            try:
                 for key in param['CF']:
                     algo = str(param["CF"][key]["CFM"])
                     if algo == "/AESV2":
@@ -1893,46 +1581,26 @@ class PDFDocument(object):
             self.decipher = self.decrypt_rc4  # XXX may be AES
         # aes
         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 V >= 4:
                 self.decipher = self.decrypt_aes
         self.ready = True
         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):
         self.is_printable = self.is_modifiable = self.is_extractable = True
         key = keyb64.decode('base64')[:16]
-        aes = AES.new(key,AES.MODE_CBC,"\x00" * len(key))
         length = int_value(param.get('Length', 0)) / 8
         rights = str_value(param.get('ADEPT_LICENSE')).decode('base64')
         rights = zlib.decompress(rights, -15)
         rights = etree.fromstring(rights)
         expr = './/{http://ns.adobe.com/adept}encryptedKey'
         bookkey = ''.join(rights.findtext(expr)).decode('base64')
-        bookkey = aes.decrypt(bookkey)
-        bookkey = bookkey[:-ord(bookkey[-1])]
-        bookkey = bookkey[-16:]
+        bookkey = unpad(AES.new(key, AES.MODE_CBC, b'\x00'*16).decrypt(bookkey), 16) # PKCS#7
+        if len(bookkey) > 16:
+            bookkey = bookkey[-16:]
         ebx_V = int_value(param.get('V', 4))
         ebx_type = int_value(param.get('EBX_ENCRYPTIONTYPE', 6))
         # added because of improper booktype / decryption book session key errors
@@ -1967,7 +1635,7 @@ class PDFDocument(object):
 
     def initialize_ebx_inept(self, password, docid, param):
         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
         rights = codecs.decode(param.get('ADEPT_LICENSE'), 'base64')
         rights = zlib.decompress(rights, -15)
@@ -1983,14 +1651,13 @@ class PDFDocument(object):
             raise ADEPTNewVersionError("Book uses new ADEPT encryption")
 
         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 (self.verify_book_key(bookkey)):
-                bookkey = bookkey[-16:]
-                length = 16
-            else:
-                raise ADEPTError('error decrypting book session key')
+        if bookkey is None:
+            raise ADEPTError('error decrypting book session key')
 
         ebx_V = int_value(param.get('V', 4))
         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,
 # returns the UUID of the user that's licensed to open this file.
 def adeptGetUserUUID(inf):
-    try: 
+    try:
         doc = PDFDocument()
         inf = open(inf, 'rb')
         pars = PDFParser(doc, inf)
@@ -2376,11 +2043,11 @@ def adeptGetUserUUID(inf):
         rights = etree.fromstring(rights)
         expr = './/{http://ns.adobe.com/adept}user'
         user_uuid = ''.join(rights.findtext(expr))
-        if user_uuid[:9] != "urn:uuid:": 
+        if user_uuid[:9] != "urn:uuid:":
             return None
         return user_uuid[9:]
 
-    except: 
+    except:
         return None
 
 ###
@@ -2585,8 +2252,6 @@ class PDFSerializer(object):
 
 
 def decryptBook(userkey, inpath, outpath, inept=True):
-    if RSA is None:
-        raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
     with open(inpath, 'rb') as inf:
         serializer = PDFSerializer(inf, userkey, inept)
         with open(outpath, 'wb') as outf:
@@ -2601,8 +2266,6 @@ def decryptBook(userkey, inpath, outpath, inept=True):
 
 
 def getPDFencryptionType(inpath):
-    if RSA is None:
-        raise ADEPTError("PyCryptodome or OpenSSL must be installed.")
     with open(inpath, 'rb') as inf:
         doc = doc = PDFDocument()
         parser = PDFParser(doc, inf)
@@ -2735,13 +2398,6 @@ def gui_main():
 
 
     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.resizable(True, False)
     root.minsize(370, 0)
index f102ec5fbf7acd0372434db124cc53344e25b1d9..f9cd879a6e0f4017728e4b25d69d56708a2cb06d 100644 (file)
@@ -30,8 +30,14 @@ import struct
 
 from io import BytesIO
 
-from Crypto.Cipher import AES
-from Crypto.Util.py3compat import bchr
+try:
+    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:
     # lzma library from calibre 4.6.0 or later
@@ -744,23 +750,6 @@ def addprottable(ion):
     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
 OBFUSCATION_TABLE = {
     "V1":    (0x00, None),
@@ -876,7 +865,7 @@ class DrmIonVoucher(object):
         key = hmac.new(sharedsecret, b"PIDv3", digestmod=hashlib.sha256).digest()
         aes = AES.new(key[:32], AES.MODE_CBC, self.cipheriv[:16])
         b = aes.decrypt(self.ciphertext)
-        b = pkcs7unpad(b, 16)
+        b = unpad(b, 16)
 
         self.drmkey = BinaryIonParser(BytesIO(b))
         addprottable(self.drmkey)
@@ -1082,7 +1071,7 @@ class DrmIon(object):
     def processpage(self, ct, civ, outpages, decompress, decrypt):
         if decrypt:
             aes = AES.new(self.key[:16], AES.MODE_CBC, civ[:16])
-            msg = pkcs7unpad(aes.decrypt(ct), 16)
+            msg = unpad(aes.decrypt(ct), 16)
         else:
             msg = ct
 
index f9f79a47f3ed66eb9d6313a83c628b4fb2e7bf0a..0ce800d8a68e4a74ee28faeebb038098ca218d07 100644 (file)
@@ -2,10 +2,10 @@
 # -*- coding: utf-8 -*-
 
 # kindlekey.py
-# Copyright © 2008-2020 Apprentice Harper et al.
+# Copyright © 2008-2022 Apprentice Harper et al.
 
 __license__ = 'GPL v3'
-__version__ = '3.0'
+__version__ = '3.1'
 
 # Revision history:
 #  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.8   - Fix for Mac OS X Big Sur
 #  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 getopt
 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:
     RegError
@@ -121,22 +132,16 @@ class DrmException(Exception):
     pass
 
 # crypto digestroutines
-import hashlib
 
 def MD5(message):
-    ctx = hashlib.md5()
-    ctx.update(message)
-    return ctx.digest()
+    return hashlib.md5(message).digest()
 
 def SHA1(message):
-    ctx = hashlib.sha1()
-    ctx.update(message)
-    return ctx.digest()
+    return hashlib.sha1(message).digest()
 
 def SHA256(message):
-    ctx = hashlib.sha256()
-    ctx.update(message)
-    return ctx.digest()
+    return hashlib.sha256(message).digest()
+
 
 # For K4M/PC 1.6.X and later
 def primes(n):
@@ -189,6 +194,12 @@ def decode(data,map):
         result += pack('B',value)
     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
 if iswindows:
     from ctypes import windll, c_char_p, c_wchar_p, c_uint, POINTER, byref, \
@@ -205,636 +216,6 @@ if iswindows:
     advapi32 = windll.advapi32
     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.
     # Probably supposed to act as obfuscation
     charMap2 = b"AaZzB0bYyCc1XxDdW2wEeVv3FfUuG4g-TtHh5SsIiR6rJjQq7KkPpL8lOoMm9Nn_"
@@ -1080,7 +461,7 @@ if iswindows:
             salt = str(0x6d8 * int(build)).encode('utf-8') + guid
             sp = GetUserName() + b'+@#$%+' + GetIDString().encode('utf-8')
             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
         while len(items) > 0:
@@ -1143,8 +524,6 @@ if iswindows:
                 entropy = SHA1(keyhash) + added_entropy
                 cleartext = CryptUnprotectData(encryptedValue, entropy, 1)
             elif version == 6:
-                from Crypto.Cipher import AES
-                from Crypto.Util import Counter
                 # decode using new testMap8 to get IV + ciphertext
                 iv_ciphertext = decode(encdata, testMap8)
                 # pad IV so that we can substitute AES-CTR for GCM
@@ -1174,114 +553,8 @@ if iswindows:
             DB = {}
         return DB
 elif isosx:
-    import copy
     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
     charMap1 = b'n5Pr6St7Uv8Wx9YzAb0Cd1Ef2Gh3Jk4M'
     charMap2 = b'ZB0bYyc1xDdW2wEV3Ff7KkPpL8UuGA4gz-Tme9Nn_tHh5SvXCsIiR6rJjQaqlOoM'
@@ -1411,33 +684,13 @@ elif isosx:
         #print "ID Strings:\n",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
     class CryptUnprotectData(object):
         def __init__(self, entropy, IDString):
             sp = GetUserName() + b'+@#$%+' + IDString
             passwdData = encode(SHA256(sp),charMap2)
             salt = entropy
-            self.crp = LibCrypto()
-            iter = 0x800
-            keylen = 0x400
-            key_iv = self.crp.keyivgen(passwdData, salt, iter, keylen)
+            key_iv = PBKDF2(passwdData, salt, count=0x800, dkLen=0x400)
             self.key = key_iv[0:32]
             self.iv = key_iv[32:48]
             self.crp.set_decrypt_key(self.key, self.iv)
@@ -1575,7 +828,7 @@ elif isosx:
                     salt = str(0x6d8 * int(build)).encode('utf-8') + guid
                     sp = GetUserName() + b'+@#$%+' + IDString
                     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 ("sp",sp)
@@ -1647,8 +900,6 @@ elif isosx:
                         cleartext = cud.decrypt(encryptedValue)
 
                     elif version == 6:
-                        from Crypto.Cipher import AES
-                        from Crypto.Util import Counter
                         # decode using new testMap8 to get IV + ciphertext
                         iv_ciphertext = decode(encdata, testMap8)
                         # pad IV so that we can substitute AES-CTR for GCM
index 843eebe5db18d70ca5d42cc1a090c545c53b5a09..c57b884d1b0ae76a4055a4d0b6f6396fa95c8a50 100755 (executable)
@@ -7,7 +7,7 @@
 
 from __future__ import print_function
 __license__ = 'GPL v3'
-__version__ = "1.0"
+__version__ = "1.1"
 
 # This is a python script. You need a Python interpreter to run it.
 # 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.42 - Added GPL v3 licence. updated/removed some print statements
 #  1.0  - Python 3 compatibility for calibre 5.0
+#  1.1  - Speed Python PC1 implementation up a little bit
 
 import sys
 import os
@@ -175,7 +176,7 @@ def PC1(key, src, decryption=True):
     wkey = []
     for i in range(8):
         wkey.append(key[i*2]<<8 | key[i*2+1])
-    dst = b''
+    dst = bytearray(len(src))
     for i in range(len(src)):
         temp1 = 0;
         byteXorVal = 0;
@@ -194,8 +195,8 @@ def PC1(key, src, decryption=True):
             keyXorVal = curByte * 257;
         for j in range(8):
             wkey[j] ^= keyXorVal;
-        dst+=bytes([curByte])
-    return dst
+        dst[i] = curByte
+    return bytes(dst)
 
 # accepts unicode returns unicode
 def checksumPid(s):
diff --git a/DeDRM_plugin/openssl_des.py b/DeDRM_plugin/openssl_des.py
deleted file mode 100644 (file)
index 9e455b4..0000000
+++ /dev/null
@@ -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
diff --git a/DeDRM_plugin/pycrypto_des.py b/DeDRM_plugin/pycrypto_des.py
deleted file mode 100644 (file)
index 286df9f..0000000
+++ /dev/null
@@ -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
diff --git a/DeDRM_plugin/python_des.py b/DeDRM_plugin/python_des.py
deleted file mode 100644 (file)
index bd02904..0000000
+++ /dev/null
@@ -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)
index e80e9aed0b4ab9eb07c4ade50ddfb323a4443aaf..403db271f2becc090efc38bf2d906639140cc68c 100644 (file)
@@ -1,6 +1,9 @@
 #!/usr/bin/env python3
 # -*- 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
 # Merge https://github.com/apprenticeharper/DeDRM_tools/pull/1691 to fix
 # key fetch issues on some machines.
 """Manage all Kobo books, either encrypted or DRM-free."""
 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__)
 
 import sys
@@ -180,6 +183,13 @@ import shutil
 import argparse
 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
 try:
   from xml.etree import ElementTree as ET
@@ -194,88 +204,6 @@ KOBO_HASH_KEYS = ['88b3a2e13', 'XzUhGYdFp', 'NoCanLook','QJhwzAtXL']
 class ENCRYPTIONError(Exception):
     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
 # and also make sure that any unicode strings get
 # encoded using "replace" before writing them.
@@ -630,11 +558,9 @@ class KoboFile(object):
         file page key. The caller must determine if the decrypted
         data is correct."""
         # The userkey decrypts the page key (self.key)
-        keyenc = AES(userkey)
-        decryptedkey = keyenc.decrypt(self.key)
-        # The decrypted page key decrypts the content
-        pageenc = AES(decryptedkey)
-        return self.__removeaespadding(pageenc.decrypt(contents))
+        decryptedkey = AES.new(userkey, AES.MODE_ECB).decrypt(self.key)
+        # The decrypted page key decrypts the content. Padding is PKCS#7
+        return unpad(AES.new(decryptedkey, AES.MODE_ECB).decrypt(contents), 16)
 
     def check (self, contents):
         """
@@ -704,23 +630,6 @@ class KoboFile(object):
                 raise ValueError()
         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):
     print("Converting {0}".format(book.title))