ZIPファイルのパスワード有無をVBAマクロで判定する

ExcelVBA

解決したい課題

大量のzipファイルを対象に、暗号化されているか(解凍時にパスワードが必要となるか)を確認したい局面がありました。一つ一つファイルを解凍していけばわかりますが、その手作業は面倒過ぎて取り得ません。VBAのマクロでZIPファイルを自動解凍することはできるので、解凍できればパスワードなし、(解凍先フォルダーに実ファイルが存在しない等)解凍に失敗すればパスワードがある、と判定する方法もあるでしょう。実際にWeb上で検索すると、VBAからZIPファイルを解凍するやり方として、Power Shellのコマンドを実行する方法や、7-Zip.dllをコールする方法がなど数多くがヒットします。

ただ、このやり方だと、パスワードがかかっていない場合はしっかりと解凍してしまうわけで、スマートじゃない気がするんですよね。何十、何百メガの大きいサイズのzipファイルを対象とした場合、いたずらに時間とCPUを食ってしまいます。そこで、大きなサイズのzipファイルであっても、スピーディにパスワード有無が判定できる処理を考えてみたいと思います。

解決手法の検討

一つは、Shell.Application の NameSpaceでフォルダーオブジェクトを捕まえて、folder.Itemsコレクションで格納されているアイテムを順に見ていき、folder.GetDetailsOfで取得できるパスワードの有無プロパティを確認する方法。対象がフォルダだった場合はfolder.GetDetailsOf(Item, 3)で戻ってくるプロパティ値が空白になるので、再帰で掘るようプログラミングすれば、いい感じで処理を組めそうですが、既にWeb上にYahoo知恵袋にサンプルコードが公開されていました。すばらしい!

VBA、ZIPファイル,パスワードがつけられているか確かめる方法 - .VBAで指定のZIPファイルにパスワードがつけられ... - Yahoo!知恵袋
VBA、ZIPファイル,パスワードがつけられているか確かめる方法 .VBAで指定のZIPファイルにパスワードがつけられているかどうか知る方法を探しています。OutlookVBAを使って、メール送信時、メールのあて先の中に社外のアドレスが含まれていて、添付ファイルのなかにZIPファイルが含まれていて、そのファイルにパスワ...

これを有難くこのまま使わせていただこうかなと考えたのですが、調査している過程で、ZIPのファイルフォーマット(構造)に心を奪われてしまいました。

ウィキィペディアより

さらに詳しく解説されているWebサイトがこちら。

ZIP書庫ファイル フォーマット - 略して仮。

おおっ!ZIPファイルをバイナリで開けば、暗号化の有無が判定できるでのではないでしょうか!

実現への道のり

Local file headerには、次のようにデータが格納されているようです。そして、緑字と赤字部分を参照すればよさそうです。

オフセットサイズ内容
04ローカルファイルヘッダのシグネチャ = 0x504B0304(PK\003\004)
42展開に必要なバージョン (最小バージョン)
62汎用目的のビットフラグ
※1ビット目が、パスワード保護フラグ
82圧縮メソッド
102ファイルの最終変更時間
122ファイルの最終変更日付
144CRC-32
184圧縮サイズ
224非圧縮サイズ
262ファイル名の長さ (n)
282拡張フィールドの長さ (m)
30nファイル名
30+nm拡張フィールド
Wikipediaより 赤字は追記

※汎用目的のビットフラグの1ビット目が、パスワード保護フラグとなっているとのことでした。

これらの情報から、ZIPファイルをバイナリで開き、4バイトのLocal file headerシグネチャを探し出し、その3バイト先にあるgeneral purpose bit flag(汎用目的のビットフラグ)の1ビット目を見れば、当該アイテムがパスワード保護されているか判定できそうです。色々なZIPファイルを作成して試してみたところ、このLocal file header、ZIPに圧縮されているファイルとフォルダーの数だけ作られるみたいでした。そしてフォルダーだった場合は、パスワード保護フラグは0となるようでした。ということは、フォルダーの場合は無視して、ファイルの場合にのみパスワード保護の有無を確認すればよいことになります。フォルダーかどうかの判定は、同じくLocal file headerのcompressed sizeを見て0ならフォルダーと判断できそうです。さらに、幾層もサブフォルダーがあるような複雑なフォルダー構成でフォルダーを対象にZIP化した場合も調べてみましたが、パスワード暗号化した場合は、格納されているどの階層にあるファイルも、パスワード保護フラグはすべて1がセットされていました。つまり、

・ZIPファイルをバイナリで開き、最初「ファイル」のパスワード保護フラグで判定

で解決できると確信した次第です。

VBAで実用マクロ

ということで、作成してみたマクロコードがこちらです。

次が呼び出し用サンプルで

次が判定用の関数です。

フルパスで指定されたZIPファイルにパスワードが設定されていた場合、”Pass有”を、パスワードがない場合は”Passなし”を返す関数です。フォルダーやサイズが0のファイルが圧縮されていた場合は、”実ファイルなし”を返します。複数ファイルを判定したい場合は、このFunctionプロシージャを連続で呼び出せばよいでしょう。

コードの解説

ここから先は、処理内容に興味ある方だけ読んでいただければと思います。処理としては、まずZIPファイルをバイナリーで格納できるバイト型の配列変数を確保します。念のため、ファイルサイズ分確保していますが、お目当てのLocal file headerは早々に現れるため、この配列がすべて埋まることはありません。

次に、ZIPファイルを1バイトずつバイナリで読み込み、配列に格納していきます。ZIPに格納されているアイテム(フォルダーやファイル)の冒頭に存在するLocal file headerのシグネチャサイズは4バイト = 0x504B0304(PK\003\004)なので、それをループで順に探します。andで4バイト一気に探したり、4バイト連結させて探すより、先頭のバイトから順に探して合致しない場合ループから外れる処理のほうが速度が速くなる(気がする)ので、IF文が深くなってしまいますが、こうしています。

そして、お目当てのシグネチャが見つかった場合、そのアイテムがフォルダーかファイルかを判定します。判定は、ファイルサイズが格納されているバイトを参照します。

ファイルサイズが格納されている4バイトがすべて0だった場合、文字列変数StrSizeには”0000”が格納されることになり、フォルダーかファイルか判定できます。フォルダーの場合は一律パスワード保護フラグは立たない仕様のようなので無視し、ファイルの場合のみ判定に入ります。パスワード保護のフラグが格納されているバイトの1ビット目が0であればパスなし、そうでなければ(1であれば)パス有と判定します。1ビット目の判定には単純に1バイトを2で割った余りを使用しています。判定が行われた場合、Functionプロシージャを抜けます。

もし、今までのループで、Functionプロシージャを抜けていない場合は、実体ファイルがない(フォルダーのみ、あるいは、ファイルサイズ0のファイル)状態であるため、”実ファイルなし”を返します。

この手法であれば、Zipファイルに、大量かつ巨大なファイルやフォルダーが複雑なフォルダー構成で圧縮されていようが、一瞬でパスワードの有無が判定できるのではないかと思っていますが、いかんせん、実務での使用が不足しているので、動作結果などコメントいただけると嬉しいです。

コメント

タイトルとURLをコピーしました