S-JIS[2006-11-14/2008-08-15] 変更履歴

AESによる暗号化・復号化

Javaには、Cipherという暗号化・復号化を行うクラスが用意されている。
DESとか色々な種類の暗号をこのクラスによって使うことが出来る。

AESは JDK1.4.1ではサポートされていないが、JDK1.4.2ではサポートされている。
JDK1.5でもサポートされているが、128bit以外は使えないっぽい。
JDK1.6ではjce_policyを更新すれば使える。[2008-08-15]


単純な例

秘密鍵(暗号化・復号化で同じものを使う)をバイト列で用意して使う例。

import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
	public static void main(String[] args) {
		Key skey = makeKey1(128);

		// 暗号化
		byte[] enc = encode1(args[0].getBytes(), skey);
		// 復号化
		byte[] dec = decode1(enc, skey);

		System.out.println(new String(dec));
	}

	/**
	 * 秘密鍵をバイト列から生成する
	 * @param key_bits 鍵の長さ(ビット単位)
	 */
	public static Key makeKey1(int key_bits) {
		// バイト列
		byte[] key = new byte[key_bits / 8];

		// バイト列の内容(秘密鍵の値)はプログラマーが決める
		for (int i = 0; i < key.length; i++) {
			key[i] = (byte) (i + 1);
		}

		return new SecretKeySpec(key, "AES");
	}

	/**
	 * 暗号化
	 */
	public static byte[] encode1(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.ENCRYPT_MODE, skey);
			return cipher.doFinal(src);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 復号化
	 */
	public static byte[] decode1(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES");
			cipher.init(Cipher.DECRYPT_MODE, skey);
			return cipher.doFinal(src);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

キーをランダムに生成する例

鍵をランダムで生成する例。
乱数を生成するSecureRandomのgetInstance()に指定する引数は"SHA1PRNG"にする(それしか指定できない)。

	public static void main(String[] args) {
		Key skey = makeKey2(128);

		// 暗号化
		byte[] enc = encode1(args[0].getBytes(), skey);
		// 復号化
		byte[] dec = decode1(enc, skey);

		System.out.println(new String(dec));
	}

	/**
	 * 秘密鍵をランダムに生成する
	 * @param key_bits 鍵の長さ(ビット単位)
	 */
	public static Key makeKey2(int key_bits) {
		try {
			KeyGenerator generator = KeyGenerator.getInstance("AES");
			SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
			generator.init(key_bits, random);
			return generator.generateKey();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

CBC(IVを使用する)の例

イニシャルバリュー(IV(アイブイ))を使う形式の例。
Cipher#getInstance()で、モードに「CBC」を指定する。(今までは これを省略して、デフォルトのものを使っていた)

この場合、暗号化する際にIVというものが自動的に作られ、使われる。
復号化する際には 暗号化に使用したIVが必要となるので、何らかの方法で受け渡さなければならない。

ここでは、暗号化したデータの先頭にIVをくっつけることにした。
復号化する際には、先頭16バイトをIVとして扱い、残りのデータを復号する。
(IVのサイズは16バイトっぽい。これはブロックサイズと同じなのだと思われる)

AlgorithmParameters#init()には、本来は暗号化時に取得したAlgorithmParameters#getEncoded()の返り値と同じものを渡すんだけど、正当性/将来性/互換性を全く無視して無理矢理作ってみた。(JavaVMのバージョンが変わらなければ、きっとこれで動くでしょう)

	public static void main(String[] args) {
		Key skey = makeKey1(128);

		//暗号化
		byte[] enc = encode3(args[0].getBytes(), skey);
		//復号化
		byte[] dec = decode3(enc, skey);

		System.out.println(new String(dec));
	}

	/**
	 * 暗号化
	 */
	public static byte[] encode3(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, skey);

			// AlgorithmParameters prm = cipher.getParameters();
			// System.out.println(prm);
			// byte[] prme = prm.getEncoded();
			// for (int i = 0; i < prme.length; i++) {
			// 	System.out.print(Integer.toHexString(prme[i] & 0xff) + " ");
			// }
			// System.out.println();

			byte[] iv = cipher.getIV();
			byte[] enc = cipher.doFinal(src);
			byte[] ret = new byte[iv.length + enc.length];
			System.arraycopy(iv,  0, ret, 0,         iv.length);
			System.arraycopy(enc, 0, ret, iv.length, enc.length);
			return ret;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 復号化
	 */
	public static byte[] decode3(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			final int BLOCK_SIZE = cipher.getBlockSize();

			AlgorithmParameters iv = AlgorithmParameters.getInstance("AES");
			byte[] ib = new byte[2 + BLOCK_SIZE];
			ib[0] = 4; 		// getEncoded()で取った値がこういう数字になっている
			ib[1] = (byte) BLOCK_SIZE; // 動きはするけど、これで正しいのかどうかは不明
			System.arraycopy(src, 0, ib, 2, BLOCK_SIZE);
			iv.init(ib);

			cipher.init(Cipher.DECRYPT_MODE, skey, iv);
			return cipher.doFinal(src, BLOCK_SIZE, src.length - BLOCK_SIZE);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

パディングしない例

AESは16バイトずつ暗号化する。(16バイトでブロック化)
したがって、暗号化すべきデータのサイズは16の倍数でないといけない。
PKCS5Paddingを指定すると、16の倍数でない場合に適当にデータを補ってくれる。(その結果、暗号化したデータのサイズは16の倍数となる)

暗号化するデータが16の倍数に決まっている場合、パディングを指定しないことも可能。

	/**
	 * 暗号化
	 */
	public static byte[] encode4(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, skey);

			byte[] iv = cipher.getIV();
			byte[] enc = cipher.doFinal(src);
			byte[] ret = new byte[iv.length + enc.length];
			System.arraycopy(iv, 0, ret, 0, iv.length);
			System.arraycopy(enc, 0, ret, iv.length, enc.length);
			return ret;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 復号化
	 */
	public static byte[] decode4(byte[] src, Key skey) {
		try {
			Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
			final int BLOCK_SIZE = cipher.getBlockSize();

			AlgorithmParameters iv = AlgorithmParameters.getInstance("AES");
			byte[] ib = new byte[2 + BLOCK_SIZE];
			ib[0] = 4;
			ib[1] = (byte) BLOCK_SIZE;
			System.arraycopy(src, 0, ib, 2, BLOCK_SIZE);
			iv.init(ib);

			cipher.init(Cipher.DECRYPT_MODE, skey, iv);
			return cipher.doFinal(src, BLOCK_SIZE, src.length - BLOCK_SIZE);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

この指定で“暗号化するデータのサイズ”が16の倍数でない場合、実行時に以下のような例外が発生する。

javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes

使用できる暗号

使用できる暗号の種類はJava暗号化アーキテクチャー標準アルゴリズム名のドキュメン ト(JDK1.6)に載っている。[2008-08-15]

また、以下のようにして調べることも出来る。

	Set names = Security.getAlgorithms("Cipher");
	for (Iterator i = names.iterator(); i.hasNext();) {
		String name = (String) i.next();
		System.out.println(name);
	}

また、JDK1.5以降では「int bit = Cipher.getMaxAllowedKeyLength(name);」で使用可能な(最大の)鍵の長さを取得することが出来る。


無制限強度のポリシー

AESの鍵の長さは128bit・192bit・256bitの3種類あるが、デフォルトでは128bitしか使えない。[2006-11-18]
これは、(AESの規格を定めた)アメリカの輸出規制か何かの制限によるものらしい。
使おうとすると、Cipher#init()の実行時に以下のような例外が発生する。

java.lang.SecurityException: Unsupported keysize or algorithm parameters

が、日本ではアメリカと何かの協定を結んでいるらしく、Javaのポリシーファイルを書き換えれば256bitも使える。

  1. JCE(Java Cryptography Extension)の無制限強度の管轄ポリシーファイルをダウンロードする。
    バージョン ダウンロード元 ファイル名 更新日
    JDK1.4.2 ダウンロード Java2 SDKの下の方の「その他のダウンロード」 jce_policy-1_4_2.zip 2006-11-18
    JDK1.5 Java SE Downloadsの下の方の「その他のダウンロード」 jce_policy-1_5_0.zip 2006-11-18
    JDK1.6 Java SE Downloadsの下の方の「その他のダウンロード」 jce_policy-6.zip 2008-08-15
  2. ダウンロードしたファイルの中に入っている「US_export_policy.jar」と「local_policy.jar」を以下の場所にコピーする。
    (既に同名ファイルが存在しているので、上書きする)
    %JAVA_HOME%/jre/lib/security

これで、256bitの鍵も使える。
はずだが、1.4.2では確かに使えたんだけど、1.5(Java5.0)では同じようにやっても何故か出来なかった。ダウンロードしたアーカイブの中に入っているポリシーファイルの更新日がけっこう古い(2004年)なのがちょっと気になるけど…関係ないかなぁ?
JDK1.6では問題なく使えた。[2008-08-15]


Java目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま