summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authora980e066a01 <[email protected]>2022-02-22 23:16:03 +0000
committernoDRM <[email protected]>2022-03-18 15:45:39 +0000
commita1dd63ae5f48d8320b94efc79ffc2fc8e829988e (patch)
tree63eb8980b2da725716792718c93b4f0eaca8e140
parentf4634b5eabf25e4cd5a72e6fd990321b7030e120 (diff)
Remove OpenSSL support; only support PyCryptodome
This allows us to clean up the code a lot. On Windows, it isn't installed by default and most of the time not be found at all. On M1 Macs, the kernel will kill the process instead. Closes #33.
-rw-r--r--DeDRM_plugin/adobekey.py109
-rw-r--r--DeDRM_plugin/adobekey_get_passhash.py15
-rwxr-xr-xDeDRM_plugin/androidkindlekey.py41
-rwxr-xr-xDeDRM_plugin/erdr2pml.py60
-rw-r--r--DeDRM_plugin/ignoblekeyAndroid.py14
-rw-r--r--DeDRM_plugin/ignoblekeyGenPassHash.py106
-rw-r--r--DeDRM_plugin/ignoblekeyWindowsStore.py13
-rw-r--r--DeDRM_plugin/ineptepub.py318
-rwxr-xr-xDeDRM_plugin/ineptpdf.py440
-rw-r--r--DeDRM_plugin/ion.py31
-rw-r--r--DeDRM_plugin/kindlekey.py801
-rwxr-xr-xDeDRM_plugin/mobidedrm.py9
-rw-r--r--DeDRM_plugin/openssl_des.py89
-rw-r--r--DeDRM_plugin/pycrypto_des.py30
-rw-r--r--DeDRM_plugin/python_des.py220
-rw-r--r--Obok_plugin/obok/obok.py119
16 files changed, 203 insertions, 2212 deletions
diff --git a/DeDRM_plugin/adobekey.py b/DeDRM_plugin/adobekey.py
index 6155cff..36c18de 100644
--- a/DeDRM_plugin/adobekey.py
+++ b/DeDRM_plugin/adobekey.py
@@ -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)
diff --git a/DeDRM_plugin/adobekey_get_passhash.py b/DeDRM_plugin/adobekey_get_passhash.py
index 05f4c32..72b7cd5 100644
--- a/DeDRM_plugin/adobekey_get_passhash.py
+++ b/DeDRM_plugin/adobekey_get_passhash.py
@@ -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")
diff --git a/DeDRM_plugin/androidkindlekey.py b/DeDRM_plugin/androidkindlekey.py
index da34c1d..6b152f3 100755
--- a/DeDRM_plugin/androidkindlekey.py
+++ b/DeDRM_plugin/androidkindlekey.py
@@ -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
@@ -14,13 +14,14 @@
# 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 '''
diff --git a/DeDRM_plugin/erdr2pml.py b/DeDRM_plugin/erdr2pml.py
index 26c947d..6a20fdb 100755
--- a/DeDRM_plugin/erdr2pml.py
+++ b/DeDRM_plugin/erdr2pml.py
@@ -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
#
@@ -65,11 +65,17 @@
# 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]
diff --git a/DeDRM_plugin/ignoblekeyAndroid.py b/DeDRM_plugin/ignoblekeyAndroid.py
index 2b3f0ec..5fcbd6a 100644
--- a/DeDRM_plugin/ignoblekeyAndroid.py
+++ b/DeDRM_plugin/ignoblekeyAndroid.py
@@ -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")))
diff --git a/DeDRM_plugin/ignoblekeyGenPassHash.py b/DeDRM_plugin/ignoblekeyGenPassHash.py
index cb6d208..e1cdba0 100644
--- a/DeDRM_plugin/ignoblekeyGenPassHash.py
+++ b/DeDRM_plugin/ignoblekeyGenPassHash.py
@@ -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/>
@@ -31,19 +31,25 @@
# 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)
diff --git a/DeDRM_plugin/ignoblekeyWindowsStore.py b/DeDRM_plugin/ignoblekeyWindowsStore.py
index 919d2e6..f22d64d 100644
--- a/DeDRM_plugin/ignoblekeyWindowsStore.py
+++ b/DeDRM_plugin/ignoblekeyWindowsStore.py
@@ -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")))
diff --git a/DeDRM_plugin/ineptepub.py b/DeDRM_plugin/ineptepub.py
index 7f34357..5fc6d4b 100644
--- a/DeDRM_plugin/ineptepub.py
+++ b/DeDRM_plugin/ineptepub.py
@@ -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/>
@@ -31,13 +31,14 @@
# 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:]
diff --git a/DeDRM_plugin/ineptpdf.py b/DeDRM_plugin/ineptpdf.py
index 9bbf092..2364c12 100755
--- a/DeDRM_plugin/ineptpdf.py
+++ b/DeDRM_plugin/ineptpdf.py
@@ -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/>
@@ -48,27 +48,37 @@
# 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)
diff --git a/DeDRM_plugin/ion.py b/DeDRM_plugin/ion.py
index f102ec5..f9cd879 100644
--- a/DeDRM_plugin/ion.py
+++ b/DeDRM_plugin/ion.py
@@ -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
diff --git a/DeDRM_plugin/kindlekey.py b/DeDRM_plugin/kindlekey.py
index f9f79a4..0ce800d 100644
--- a/DeDRM_plugin/kindlekey.py
+++ b/DeDRM_plugin/kindlekey.py
@@ -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
diff --git a/DeDRM_plugin/mobidedrm.py b/DeDRM_plugin/mobidedrm.py
index 843eebe..c57b884 100755
--- a/DeDRM_plugin/mobidedrm.py
+++ b/DeDRM_plugin/mobidedrm.py
@@ -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
index 9e455b4..0000000
--- a/DeDRM_plugin/openssl_des.py
+++ /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
index 286df9f..0000000
--- a/DeDRM_plugin/pycrypto_des.py
+++ /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
index bd02904..0000000
--- a/DeDRM_plugin/python_des.py
+++ /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)
diff --git a/Obok_plugin/obok/obok.py b/Obok_plugin/obok/obok.py
index e80e9ae..403db27 100644
--- a/Obok_plugin/obok/obok.py
+++ b/Obok_plugin/obok/obok.py
@@ -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.
@@ -162,7 +165,7 @@
"""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))