<gh_stars>100-1000
from Crypto.Cipher import AES
from Crypto.Util import Padding

from typing import Dict
import base64
import binascii
import hashlib
import zipfile

# Based on obok.py by Physisticated.
class KoboDrmRemover:
	def __init__( self, deviceId: str, userId: str ):
		self.DeviceIdUserIdKey = KoboDrmRemover.__MakeDeviceIdUserIdKey( deviceId, userId )

	@staticmethod
	def __MakeDeviceIdUserIdKey( deviceId: str, userId: str ) -> bytes:
		deviceIdUserId = ( deviceId + userId ).encode()
		key = hashlib.sha256( deviceIdUserId ).hexdigest()
		return binascii.a2b_hex( key[ 32: ] )

	def __DecryptContents( self, contents: bytes, contentKeyBase64: str ) -> bytes:
		contentKey = base64.b64decode( contentKeyBase64 )
		keyAes = AES.new( self.DeviceIdUserIdKey, AES.MODE_ECB )
		decryptedContentKey = keyAes.decrypt( contentKey )

		contentAes = AES.new( decryptedContentKey, AES.MODE_ECB )
		decryptedContents = contentAes.decrypt( contents )
		return Padding.unpad( decryptedContents, AES.block_size, "pkcs7" )

	def RemoveDrm( self, inputPath: str, outputPath: str, contentKeys: Dict[ str, str ] ) -> None:
		with zipfile.ZipFile( inputPath, "r" ) as inputZip:
			with zipfile.ZipFile( outputPath, "w", zipfile.ZIP_DEFLATED ) as outputZip:
				for filename in inputZip.namelist():
					contents = inputZip.read( filename )
					contentKeyBase64 = contentKeys.get( filename, None )
					if contentKeyBase64 is not None:
						contents = self.__DecryptContents( contents, contentKeyBase64 )
					outputZip.writestr( filename, contents )

from Crypto.Cipher import AES
from Crypto.Util import Padding

from typing import Dict
import base64
import binascii
import hashlib
import zipfile

# Based on obok.py by Physisticated.
class KoboDrmRemover:
	def __init__( self, deviceId: str, userId: str ):
		self.DeviceIdUserIdKey = KoboDrmRemover.__MakeDeviceIdUserIdKey( deviceId, userId )

	@staticmethod
	def __MakeDeviceIdUserIdKey( deviceId: str, userId: str ) -> bytes:
		deviceIdUserId = ( deviceId + userId ).encode()
		key = hashlib.sha256( deviceIdUserId ).hexdigest()
		return binascii.a2b_hex( key[ 32: ] )

	def __DecryptContents( self, contents: bytes, contentKeyBase64: str ) -> bytes:
		contentKey = base64.b64decode( contentKeyBase64 )
		keyAes = AES.new( self.DeviceIdUserIdKey, AES.MODE_ECB )
		decryptedContentKey = keyAes.decrypt( contentKey )

		contentAes = AES.new( decryptedContentKey, AES.MODE_ECB )
		decryptedContents = contentAes.decrypt( contents )
		return Padding.unpad( decryptedContents, AES.block_size, "pkcs7" )

	def RemoveDrm( self, inputPath: str, outputPath: str, contentKeys: Dict[ str, str ] ) -> None:
		with zipfile.ZipFile( inputPath, "r" ) as inputZip:
			with zipfile.ZipFile( outputPath, "w", zipfile.ZIP_DEFLATED ) as outputZip:
				for filename in inputZip.namelist():
					contents = inputZip.read( filename )
					contentKeyBase64 = contentKeys.get( filename, None )
					if contentKeyBase64 is not None:
						contents = self.__DecryptContents( contents, contentKeyBase64 )
					outputZip.writestr( filename, contents )
# Based on obok.py by Physisticated.