ZipArchiveでハマってしまった件


この記事は[コネヒトの Advent Calendar 2021 - Qiita](https://qiita.com/advent-calendar/2021/connehito) 12日目の記事です。

今回は私自身がハマった [PHP: ZipArchive](https://www.php.net/manual/ja/class.ziparchive.php)クラスについて紹介しようと思います。

.

検証用のコードはGitHubにアップしてあります。

- [labo/php/zip_archive at main · satoshi-nishinaka/labo](https://github.com/satoshi-nishinaka/labo/tree/main/php/zip_archive)

## どうしたの？ なにがあったの？

ZipArchiveとは名前の通り、複数ファイルを纏めてZipファイル化するための機能がまとめられたクラスです。

業務でパスワード付きZipファイルを作成する事があるのですが、今回ハマったのはこのパスワード設定についてです。

結論から言うと、リリース前の確認を行っているタイミングで、 **「Macだと開けるけどWindowsだと開けないよ😇」** という事象が起きたんです…

ECS上で動いているDockerコンテナの中でレポートをまとめてZipファイルを日々作成しています。
元々、PHP7.1で動かしていたコンテナですが、今はPHP7.2以降にバージョンアップ済みなので、[PHP: ZipArchive::setEncryptionName - Manual](https://www.php.net/manual/ja/ziparchive.setencryptionname.php)のマニュアル上にある `PHP7.2以上` という記述を見て、「じゃぁ setEncryptionName を使ってパスワード付きZipファイルを作るように修正しよう！」と意気込んで改修したものの、 **ファイルが解凍できない** というトラブルに直面したのです…！！！

## 今までやっていたパスワードを設定したZipファイルの作成

実際の実装とは異なりますが、要約するとこんな感じ↓で元々Zipファイルの作成を行っていました。

ZipArchiveクラスを利用してZipファイルの作成まではPHPの機能を使い、パスワードの設定だけはシェルコマンドを利用していました。


```php
<?php

function archiveZipFile(string $outputFilePath, SplFileObject $file, string $password): bool
{
    $zip = new ZipArchive();
    if ($zip->open($outputFilePath, ZipArchive::CREATE | ZIPARCHIVE::OVERWRITE) !== true) {
        return false;
    }

    if (!$zip->addFile($file->getPathname(), $file->getFilename())) {
        return false;
    }

    if (!$zip->close()) {
        return false;
    }

    exec("zip -jP {$password} {$outputFilePath} {$file->getPathname()}");

    return true;
}

$directory = dirname(__FILE__) . '/output';
if (!file_exists($directory)) {
    mkdir($directory);
}

$file = new SplFileObject("{$directory}/example71.txt", 'w');
$file->fwrite(str_repeat('x', 256));

$result = archiveZipFile("{$directory}/example71.zip", $file, 'password');

echo $result ? 'Success' : 'Failed';
```

## ZipArchiveの機能を使ってパスワードを設定

PHP7.2以降で使える [setEncryptionName](https://www.php.net/manual/ja/ziparchive.setencryptionname.php) を使ってパスワードを設定する処理に変えてみました。

ところが、前述の通り　**「Macだと開けるけどWindowsだと開けないよ😇」**　という事象が起きてしまいました。


```php
<?php

function archiveZipFile(string $outputFilePath, SplFileObject $file, string $password): bool
{
    $zip = new \ZipArchive();
    if ($zip->open($outputFilePath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
        return false;
    }

    $zip->setPassword($password);
    if (!$zip->addFile($file->getPathname(), $file->getFilename())) {
        return false;
    }

    $zip->setEncryptionName($file->getFilename(), \ZipArchive::EM_AES_256);

    if (!$zip->close()) {
        return false;
    }

    return true;
}

$directory = dirname(__FILE__) . '/output';
if (!file_exists($directory)) {
    mkdir($directory);
}

$file = new SplFileObject("{$directory}/example72-failed.txt", 'w');
$file->fwrite(str_repeat('x', 256));

$result = archiveZipFile("{$directory}/example72-failed.zip", $file, 'password');

echo $result ? 'Success' : 'Failed';
```

## 原因: Linux上で作成したパスワード付きZipファイルはWindowsのエクスプローラ上からは解凍できない

結論: **マニュアルをちゃんと読みましょう。**

[setEncryptionName](https://www.php.net/manual/ja/ziparchive.setencryptionname.php)のページをスクロールしていったときに下の方にある　**"User Contributed Notes"** に以下の２つのコメントが残されていました。

> Files compressed using this function on Linux won't be decompressed using Windows.
>
> There seems to be some incompatibility with Windows built-in decompressor.
>
> There's an alternative library that works better here: https://github.com/Ne-Lexa/php-zip
>
> I got windows to open a file created with it by using the PKWARE encryption method

GoogleTranslateで要約すると…

> Linuxでこの機能を使用して圧縮されたファイルは、Windowsを使用して解凍されません。
> 
> Windowsの組み込みデコンプレッサとの互換性がないようです。
> 
> ここでより適切に機能する代替ライブラリがあります：https：//github.com/Ne-Lexa/php-zip
>
> PKWARE暗号化方式を使用して作成されたファイルを開くためのウィンドウを取得しました

また、もう１件の方は

> On windows is the "EM_AES_256" by default not supported, but you can use winrar, winzip or 7zip.
> 
> At first we had a password of 128 chars (this was to long) and all extract applications give an error that the password was incorrect.
> 
> The next time we did use a password of 52 chars., this time i did work!

こちらも同じくGoogleTranslateで翻訳すると…

> Windowsでは、デフォルトでは「EM_AES_256」はサポートされていませんが、winrar、winzip、または7zipを使用できます。
>
> 最初は128文字のパスワードがあり（これは長すぎました）、すべての抽出アプリケーションで、パスワードが正しくないというエラーが表示されます。
> 
> 次回52文字のパスワードを使用したときは、今回は作業しました。

結論から既に書いてしまいましたが、WindowsではEM_AES_256はサポートされていないため、エクスプローラなどで直接Zipファイルを解凍できないということでした。
ちなみに　[定義済み定数](https://www.php.net/manual/ja/zip.constants.php)　に記載されている他の暗号方式でファイルを作成しても同様にWindows下では解凍ができませんでした。

素直に前述のシェルを使う方式か、代替ライブラリを使うのが良さそうです😇