S-JIS[2007-12-14/2009-12-20] 変更履歴

Zipユーティリティークラス

Info-ZIPzipcloak(zipファイルの暗号化・復号化を行うコマンド)をJavaに移植したものです。
詳細はJavadoc参照。

hmzip14.jar(ソース付き) (56.8kB) JDK1.4以降 [/2007-12-21] (もうメンテしてません)
hmzip16.jar(ソース付き) (63.7kB) JDK1.6以降 [/2009-12-20]  

製作の経緯

Javaでzipファイルを作ったり読み込んだりするライブラリは簡単に使える著名なものがありますが、パスワード付きzipファイルには対応していません。
そこで暗号化する方法がないか探してみたところ、Info-ZIPがC言語で提供しているソースがあったので暗号化・復号化の部分(zipcloak)だけとりあえず移植してみました。(zip圧縮ファイルにパスワードを設定したり外したりする)

ちなみにC言語版は色んなプラットフォームに対応しているようですX68000まで!)が、今回はWin32用を基にしました。

ついでにそれを元に、Apache Antのzipツールクラスを参考(ほとんどコピペ(汗))にしてJava標準ライブラリを模したクラスも作ってみました。[2007-12-21]


ライセンス

こういった英語(というか外国語全般)は異様に苦手なんですが、Info-ZIPのライセンスはソースの改変を許しているみたいなので移植もたぶんOK?だといいなぁ…。

改変部分をソース上に示すっていうのがかなり無理なんですが…別言語に移植したものだからほぼ全て変更なので。
(コメントは極力残したけど…英語の内容は気にしていないので、コメントの意味とjavaソースの内容が乖離しているだろーなー)
(しかもEclipseソース整形機能でコメントすら勝手に改行されるし)

参考: Autodeskさんの法的な注意事項 …Info-ZIPのライセンスの日本語訳?


使用例(zipファイル to zipファイル)

通常のzipファイルから暗号化したzipファイルを作成する例です。

import jp.hishidama.zip.ZipCloak;
	File src = new File("sample.zip");
	File dst = new File("encode.zip");
	String password = "hoge";

	ZipCloak zip = new ZipCloak(src);
	zip.encrypt(dst, password.getBytes("MS932"));

※パスワードはbyte配列なので、Stringからの変換が必要です。


暗号化したzipファイルを復号化してパスワード無しのzipファイルを作成する例です。

	File src = new File("encode.zip");
	File dst = new File("decode.zip");
	String password = "hoge";

	ZipCloak zip = new ZipCloak(src);
	zip.decrypt(dst, password.getBytes("MS932"));

パスワードが間違っているとZipExceptionが発生します(java.utilのZipExceptionクラスを使っています)。

Exception in thread "main" java.util.zip.ZipException: password miss
	at jp.hishidama.zip.InfoZIP_Crypt.barehead(InfoZIP_Crypt.java:293)
	at jp.hishidama.zip.InfoZIP_Crypt.zipbare(InfoZIP_Crypt.java:242)

パスワード付きzipファイルを作成する例

パスワード付きzipファイル(ストリーム)を生成する例です。[2007-12-21]

import jp.hishidama.zip.ZipEntry;
import jp.hishidama.zip.ZipOutputStream;
	ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("zipファイル名")), "MS932");
	zos.setPassword(password.getBytes("MS932"));

	ZipEntry ze = new ZipEntry("test/a.txt");
	zos.putNextEntry(ze);
	zos.write("aaa".getBytes());

	ze = new ZipEntry("test/b.txt");
	zos.putNextEntry(ze);
	zos.write("bbb".getBytes());

	zos.close();

パスワード付きzipファイルを読み込む

パスワード付きzipファイルを読み込む例(JDK1.6)です。[2007-12-21/2008-12-21]

import jp.hishidama.zip.ZipEntry;
import jp.hishidama.zip.ZipFile;
	ZipFile zf = new ZipFile(new File("zipファイル名"), "MS932");
	zf.setPassword(password.getBytes("MS932"));
	zf.setCheckCrc(true); //since 2008-12-21

	for (Iterator<ZipEntry> i = zf.getEntriesIterator(); i.hasNext();) {
		ZipEntry ze = i.next();
		InputStream is = zf.getInputStream(ze);

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		for (;;) {
			int c = is.read();
			if (c == -1) break;
			bos.write(c);
		}
		is.close();

		System.out.println(bos.size());
	}
	zf.close();

パスワードが不正だった場合、以下のような例外が発生する可能性があります。[2008-12-21]

発生箇所 発生する可能性のある例外 説明
getInputStream() jp.hishidama.zip.ZipPasswordException password miss パスワードが不一致。
read() java.util.zip.ZipException incomplete dynamic bit lengths tree パスワードが不正だった為に
後続処理内でデータ復元が変になり
正しいデータとして扱えない。
invalid block type
invalid distance code
invalid literal/length code
invalid stored block lengths
oversubscribed dynamic bit lengths tree
too many length or distance symbols
java.io.EOFException Unexpected end of ZLIB input stream
jp.hishidama.zip.ZipCrcException CRC unmatch. crc=XXXXXXXX entry=YYYYYYYY データのCRCが期待される値と不一致。
setCheckCrc()をtrueにしている場合のみ発生する)

パスワードが不一致だった場合、基本的にはZipPasswordExceptionが発生します。
ただ、これはヘッダー部のCRCが一致するかどうかチェックしているだけなので、たまたまCRCが一致してこの例外が発生せず、先の処理に進むことがあります。この場合は他の例外が発生する可能性が高いです。

ZipCrcExceptionは、setCheckCrc()をtrueにしている場合しか発生しません。 これは、復元したデータのCRCがzipファイル内に保持されているCRCと等しいかどうかをチェックし、不一致の場合に発生するものです。
falseになっている場合(デフォルト)は、パスワード不正状態で復元自体は正常終了したとしても、データは正しくない可能性が高いです。データが正しいかどうかを自分でチェックした方が良いでしょう。
復元したデータのCRCが正しいかどうかをチェックする方法(setCheckCrc()がtrueになっていると、このチェックを内部で行います)

データのCRCチェックは暗号化されていないzipファイルでも行うことが出来ますが、java.util.zip.ZipFile等では行っていない為、当ライブラリーもデフォルトでは行いません。


備考

ZipCloakクラスzipファイルから別のzipファイルを作り出すものであり、圧縮・解凍自体は別の方法で行う必要があります。
Streamを使っていないのは、C言語のソースではfseek()・ftell()を使っているのでRandomAccessFileがちょうどよかったからです。

ZipCloakクラスにはオプションがいくつか設定できるようになっていますが、これに何の意味があるかは知りません(爆)
元々のzipcloakコマンドのオプションに該当しているはずですので、そちらの説明を見て下さい。

標準ライブラリにはzipをストリームで読み込むZipInputStreamというクラスもありますが、これの復号化版は完成していません。[2007-12-21]
おおよそは作れたのですが、復号化前に圧縮サイズを取得する必要があり、ストリームでは出来ないのであきらめました。(暗号データの後のフッター部に圧縮サイズが書かれているから。PushbackInputStreamを上手く使えば出来るかもしれないけど、バッファサイズの問題が…)


変更履歴

更新日 変更内容 旧バージョン
2007-12-14 ZipCloakを作成。  
2007-12-21 ZipFileZipOutputStreamを作成。  
2008-01-19 JDK1.6用のアーカイブを作成。これに伴って今までのhmzip.jarをhmzip14.jarに改名。
表面上の変更点はジェネリクスを使うようにしたもの。
内部的にはDeflater#getTotalIn()(int型)をgetBytesRead()(long型)に変更。
(コンパイルし直せばJDK1.5でも使えるはず)
 
2008-08-20 JDK1.6用のアーカイブで、package.htmlをpackage-info.javaに変更しただけ。  
2008-12-21 ZipPasswordExceptionとZipCrcExceptionを追加。(JDK1.4版はメンテしていません)  
2008-12-22 CheckedInputStreamを使ってCrcInputStreamを書き直し。
ZipCrcExceptionのコンストラクターを修正。
ZipFile#entries()の戻り型がJDKと合ってなかったので訂正。
hmzip16_20081222.jar
2009-12-20 ZipEntryの中でencodingを保持するのをやめ、文字数チェックはZipOutputStreamの中で行うよう修正。
(したがって、ZipFileでエンコードにJISAutoDetectを指定しても例外が発生しなくなった)
 

自作ソフトへ戻る / 技術メモ(zip圧縮・読込方法)へ行く
メールの送信先:ひしだま