Make decryption actually work incl. decompression
authorChristian Weiske <cweiske@cweiske.de>
Fri, 31 Dec 2021 13:18:57 +0000 (14:18 +0100)
committerChristian Weiske <cweiske@cweiske.de>
Fri, 31 Dec 2021 13:18:57 +0000 (14:18 +0100)
README.rst
src/Command/Decrypt.php
src/ZipWrapper.php

index b4454475b2b2f4023a397327a5d0fa2ef118649c..a1381fc94caf8a28694c0ec2cbea44e00898cf4b 100644 (file)
@@ -1,6 +1,6 @@
-****************************************************
-epubpypt - epub ebook encryption and decryption tool
-****************************************************
+********************************************************
+epubpypt - epub ebook encryption and obfuscation toolkit
+********************************************************
 
 
 
index 3c950bdbf5e2b8086d5433d250f79d5b3630b237..a49d0894baffb59b9d30a4238801cbe422c35baa 100644 (file)
@@ -41,7 +41,7 @@ class Decrypt
         $this->validateOptions();
         $this->loadEncryptionKey();
 
-        $zw = new ZipWrapper($this->epubFile, false);
+        $zw = new ZipWrapper($this->epubFile, $this->options['stdout']);
 
         $encryptionXml = $zw->getFromName('META-INF/encryption.xml');
         if ($encryptionXml === false) {
@@ -52,8 +52,14 @@ class Decrypt
 
         $decryptedFiles = [];
         $encInfoMap = $this->loadEncryptionInfo($zw, $sxEnc);
-        foreach ($encInfoMap as $filePath => $algoUrl) {
+        foreach ($encInfoMap as $filePath => $fileData) {
             $content = $zw->getFromName($filePath);
+            if ($content === false) {
+                $this->logger->error('Could not read contents of ' . $filePath);
+                continue;
+            }
+
+            $algoUrl = $fileData['algoUrl'];
             if ($algoUrl == 'http://www.w3.org/2001/04/xmlenc#aes256-cbc') {
                 $this->logger->info('Decrypting ' . $filePath);
                 $decryptedContent = Crypt::decryptAES256CBC(
@@ -61,6 +67,10 @@ class Decrypt
                     $content
                 );
 
+            } else if ($algoUrl == 'http://www.idpf.org/2008/embedding') {
+                //obfuscation algorithm; skip
+                continue;
+
             } else {
                 $this->logger->error('Unsupported encryption algorithm: ' . $algoUrl);
                 continue;
@@ -68,7 +78,32 @@ class Decrypt
 
             if ($decryptedContent === null) {
                 $this->logger->error('Decryption failed for ' . $filePath);
-                continue;
+                return;
+            }
+
+            if ($fileData['compressionMethod'] == 8) {
+                $decryptedContent = @gzinflate($decryptedContent);
+                if ($decryptedContent === false) {
+                    $this->logger->error('Decompression failed for ' . $filePath);
+                    return;
+                }
+            }
+            if ($fileData['originalLength'] !== null
+                && strlen($decryptedContent) != $fileData['originalLength']
+            ) {
+                $this->logger->error(
+                    "Amount of decrypted data is wrong:\n"
+                    . $filePath . ' should be ' . $fileData['originalLength'] . ' bytes'
+                    . ' but we decrypted ' . strlen($decryptedContent),
+                    23
+                );
+                return;
+            }
+
+            if ($this->options['stdout']) {
+                echo $decryptedContent;
+                $zw->close();
+                return;
             }
 
             $decryptedFiles[] = $filePath;
@@ -76,7 +111,11 @@ class Decrypt
         }
 
         $this->removeDecryptedFromEncryptionXml($sxEnc, $decryptedFiles);
-        $zw->addFromString('META-INF/encryption.xml', $sxEnc->asXML());
+        if ($sxEnc->count() == 0) {
+            $zw->deleteName('META-INF/encryption.xml');
+        } else {
+            $zw->addFromString('META-INF/encryption.xml', $sxEnc->asXML());
+        }
 
         if (!$zw->close()) {
             $this->logger->error('Error while writing zip file contents');
@@ -100,7 +139,8 @@ class Decrypt
     }
 
     /**
-     * @return array Key is the file path, value the encryption algorithm URL.
+     * @return array Key is the file path, value an array with encryption and
+     *               compression information.
      */
     protected function loadEncryptionInfo(ZipWrapper $zw, \SimpleXMLElement $sxEnc): array
     {
@@ -109,8 +149,21 @@ class Decrypt
         foreach ($sxEnc as $sxEncData) {
             $filePath = (string) $sxEncData->CipherData->CipherReference['URI'];
             $algoUrl  = (string) $sxEncData->EncryptionMethod['Algorithm'];
+
+            if (isset($sxEncData->EncryptionProperties->EncryptionProperty->Compression)) {
+                $compressionMethod = (string) $sxEncData->EncryptionProperties->EncryptionProperty->Compression['Method'];
+                $originalLength = (string) $sxEncData->EncryptionProperties->EncryptionProperty->Compression['OriginalLength'];
+            } else {
+                $compressionMethod = 0;
+                $originalLength = null;
+            }
+
             if ($this->options['all'] || isset($invPaths[$filePath])) {
-                $encInfoMap[$filePath] = $algoUrl;
+                $encInfoMap[$filePath] = [
+                    'algoUrl'           => $algoUrl,
+                    'compressionMethod' => $compressionMethod,
+                    'originalLength'    => $originalLength,
+                ];
                 unset($invPaths[$filePath]);
             }
         }
@@ -145,6 +198,18 @@ class Decrypt
             );
         }
 
+        if (!$this->options['all'] && !count($this->paths)) {
+            throw new \DomainException(
+                'Use --all or specify at least one file to decyrpt.'
+            );
+        }
+
+        if ($this->options['all'] && $this->options['stdout']) {
+            throw new \DomainException(
+                '--all cannot be used with --stdout.'
+            );
+        }
+
         if ($this->options['key'] !== null
             && $this->options['keyfile'] !== null
         ) {
index 0438f62b18e501b71015b895ffe1465323052f54..d37fb1565e91846eacab2c749a425f80c8555601 100644 (file)
@@ -75,10 +75,24 @@ class ZipWrapper
         }
     }
 
+    /**
+     * Delete a file
+     */
+    public function deleteName($filePath): bool
+    {
+        if ($this->zip) {
+            return $this->zip->deleteName($filePath);
+        } else {
+            return unlink($this->dirPath . $filePath);
+        }
+    }
+
     /**
      * Get content of given file path
+     *
+     * @return string|false false on error
      */
-    public function getFromName($filePath): string
+    public function getFromName($filePath)
     {
         if ($this->zip) {
             return $this->zip->getFromName($filePath);