2 namespace Epubpypt\Command;
5 use Epubpypt\ZipWrapper;
8 * Decrypt files inside the epub file
13 * Path of the .epub file
15 protected string $epubFile;
18 * Paths of files to decrypt
20 protected array $paths;
23 * Command line options
25 protected array $options = [];
28 * Binary encryption key
30 protected ?string $encryptionKey = null;
32 public function __construct(string $epubFile, array $paths, array $options)
34 $this->epubFile = $epubFile;
35 $this->paths = $paths;
36 $this->options = $options;
39 public function run(): void
41 $this->validateOptions();
42 $this->loadEncryptionKey();
44 $zw = new ZipWrapper($this->epubFile, false);
46 $encryptionXml = $zw->getFromName('META-INF/encryption.xml');
47 if ($encryptionXml === false) {
48 $this->logger->info('No encryption information file found');
51 $sxEnc = simplexml_load_string($encryptionXml);
54 $encInfoMap = $this->loadEncryptionInfo($zw, $sxEnc);
55 foreach ($encInfoMap as $filePath => $algoUrl) {
56 $content = $zw->getFromName($filePath);
57 if ($algoUrl == 'http://www.w3.org/2001/04/xmlenc#aes256-cbc') {
58 $this->logger->info('Decrypting ' . $filePath);
59 $decryptedContent = Crypt::decryptAES256CBC(
65 $this->logger->error('Unsupported encryption algorithm: ' . $algoUrl);
69 if ($decryptedContent === null) {
70 $this->logger->error('Decryption failed for ' . $filePath);
74 $decryptedFiles[] = $filePath;
75 $zw->addFromString($filePath, $decryptedContent);
78 $this->removeDecryptedFromEncryptionXml($sxEnc, $decryptedFiles);
79 $zw->addFromString('META-INF/encryption.xml', $sxEnc->asXML());
82 $this->logger->error('Error while writing zip file contents');
86 protected function loadEncryptionKey(): void
88 if ($this->options['key'] !== null) {
89 $this->encryptionKey = @hex2bin($this->options['key']);
90 if ($this->encryptionKey == '') {
91 throw new \InvalidArgumentException('Invalid encryption key');
95 if (!is_readable($this->options['keyfile'])) {
96 throw new \InvalidArgumentException('Cannot open key file');
98 $this->encryptionKey = file_get_contents($this->options['keyfile']);
103 * @return array Key is the file path, value the encryption algorithm URL.
105 protected function loadEncryptionInfo(ZipWrapper $zw, \SimpleXMLElement $sxEnc): array
108 $invPaths = array_flip($this->paths);
109 foreach ($sxEnc as $sxEncData) {
110 $filePath = (string) $sxEncData->CipherData->CipherReference['URI'];
111 $algoUrl = (string) $sxEncData->EncryptionMethod['Algorithm'];
112 if ($this->options['all'] || isset($invPaths[$filePath])) {
113 $encInfoMap[$filePath] = $algoUrl;
114 unset($invPaths[$filePath]);
118 if (!$this->options['all'] && count($invPaths)) {
119 foreach ($invPaths as $filePath => $dummy) {
120 $this->logger->notice('File is not encrypted: ' . $filePath);
127 protected function removeDecryptedFromEncryptionXml(
128 \SimpleXMLElement $sxEnc, $decryptedFiles
130 $decryptedFiles = array_flip($decryptedFiles);
131 $total = count($sxEnc->EncryptedData);
132 for ($n = $total - 1; $n >= 0; $n--) {
133 $filePath = (string) $sxEnc->EncryptedData[$n]->CipherData->CipherReference['URI'];
134 if (isset($decryptedFiles[$filePath])) {
135 unset($sxEnc->EncryptedData[$n]);
140 protected function validateOptions(): void
142 if ($this->options['all'] && count($this->paths)) {
143 throw new \DomainException(
144 'Do not specify file paths with using option "--all".'
148 if ($this->options['key'] !== null
149 && $this->options['keyfile'] !== null
151 throw new \DomainException(
152 'Specify either --key or --keyfile option, but not both.'
156 if ($this->options['key'] === null
157 && $this->options['keyfile'] === null
159 throw new \DomainException(
160 'Specify one of --key or --keyfile options.'