[Java]MD5、DESで暗号化する

5月 08 2011 Published by たけし under Java, プログラミング

Yellow computer folder with key. Isolated 3d imageJavaで文字列の暗号化をする処理が必要になったので、ライブラリクラスを作ってみました。

とりあえず、非可逆的な暗号化方式としてMD5を。可逆的な暗号化方式としてDESを選択。(※可逆的な暗号化というのは、暗号化された文字列を元の文字列に復号化できる方式。非可逆的な暗号化というのは、もう元には復号化できない方式です。)

引数として渡された文字列を、MD5やDESで暗号化してバイナリ配列を作り、それを16進数表記の文字列として返す。というメソッドを実装しました。

  

Javaには標準で暗号化のための便利なAPIが用意されているんですね。作るのも簡単です。
 


また、作成にあたってはJavaのAPIリファレンスの他、この辺りのHPも参考にさせて頂きました。

まずは、MD5に暗号化するためのクラス。。。

[MD5.java]

package jp.creativegear.util.encryption;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5で暗号化するクラス
 *
 * @author CreativeGear
 */
public class MD5 {

	/**
	 * MD5で文字列を暗号化し、暗号化されたバイナリを16進数表記の文字列に変換した値を取得する
	 *
	 * @param str
	 *            暗号化対象の文字列
	 * @return 暗号化した結果を16進数表記に変換した文字列
	 */
	public static String digestMd5(String str) throws NoSuchAlgorithmException {
		if (str == null || str.length() == 0) {
			throw new IllegalArgumentException("文字列がNull、または空です。");
		}

		// MD5で暗号化したByte型配列を取得する
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		md5.update(str.getBytes());
		byte[] enclyptedHash = md5.digest();

		// 暗号化されたByte型配列を、16進数表記文字列に変換する
		return BinaryHexConverter.bytesToHexString(enclyptedHash);
	}
}

 

インスタンス変数を持つ必要が無かったので、メソッドはStaticsにしています。このクラスはインスタンスを作成せずに利用できます。

JavaAPIでの暗号化は、基本的にはByte型配列を読み込んで、それを暗号化したByte型配列を返します。ただ、そのままでは扱いづらいですので、Byte型配列の中身を16進数表記に変換した文字列を作成して、それを返却するようにしています。

Byte型配列を文字列に変換する方法としては、他にもBase64でエンコードする方法等がありますが、今回は16進数表記文字列に変換する方法を採りました。

 

Byte型配列を16進数表記文字列に変換するためのクラスは、別途用意してあります。

[BinaryHexConverter.java]

package jp.creativegear.util.encryption;

/**
 * Byte型配列⇔16進数表記String型への変換
 * @author CreativeGear
 */
public class BinaryHexConverter {

	/**
	 * Byte型配列から16進数表記文字列へ変換する
	 * @param fromByte 変換対象Byte型配列
	 * @return 16進数表記に変換後の文字列
	 */
	public static String bytesToHexString(byte[] fromByte) {

		StringBuilder hexStrBuilder = new StringBuilder();
		for (int i = 0; i < fromByte.length; i++) {

			// 16進数表記で1桁数値だった場合、2桁目を0で埋める
			if ((fromByte[i] & 0xff) < 0x10) {
				hexStrBuilder.append("0");
			}
			hexStrBuilder.append(Integer.toHexString(0xff & fromByte[i]));
		}

		return hexStrBuilder.toString();
	}

	/**
	 * 16進数表記文字列からByte型配列へ変換する
	 * @param fromHexStr 変換対象の16進数表記文字列
	 * @return 変換後のByte型配列
	 */
	public static byte[] HexStringToBytes(String fromHexStr) {

		//16進数表記では2文字で1バイトを表現するため、
		//Byte型配列に変換する際には、配列の長さは1/2で良い
		byte[] toByte = new byte[fromHexStr.length() / 2];

		//16進数表記文字列を、2文字ずつByte型へ変換していく
		for (int i = 0; i < toByte.length; i++) {
			toByte[i] = (byte) Integer.parseInt(fromHexStr.substring(i * 2, (i + 1) * 2), 16);
		}
		return toByte;
	}
}

 

次に、DESで暗号化&復号化を行うクラスも作りました。

[DES.java]

package jp.creativegear.util.encryption;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

public class DES {

	private SecretKey secretKey;

	public String encryptDes(String clearText) throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

		KeyGenerator keyGen = KeyGenerator.getInstance("DES");
		secretKey = keyGen.generateKey();
		keyGen.init(56);

		byte[] clearTextByte = clearText.getBytes();

		Cipher cipher = Cipher.getInstance("DES");
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);

		byte[] encryptedBytes = cipher.doFinal(clearTextByte);
		String encryptedHexString = BinaryHexConverter.bytesToHexString(encryptedBytes);

		return encryptedHexString;
	}

	public  String decryptDes(String encryptedHexText) throws NoSuchAlgorithmException, NoSuchPaddingException,
			InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

		Cipher cipher = Cipher.getInstance("DES");
		cipher.init(Cipher.DECRYPT_MODE, secretKey);

		byte[] decryptedByte = cipher.doFinal(BinaryHexConverter.HexStringToBytes(encryptedHexText));
		String decryptedText = new String(decryptedByte);

		return decryptedText;
	}

}

 

こちらは秘密鍵を暗号化時と復号化時で共有するため、それをフィールドに定義しています。ですので、こちらはインスタンスを作成して利用する必要があります。

 

実際の使い方はこのような感じです。

[test.java]

package jp.creativegear.util.encryption;

public class test {

	public static void main(String[] args) {

		String str = "Hello!";
		try {
			System.out.println("元の文字列: " + str);
			System.out.println("MD5で暗号化: " + MD5.digestMd5(str));

			DES des = new DES();
			String encryptedStr = des.encryptDes(str);
			System.out.println("DESで暗号化: " + encryptedStr);
			System.out.println("DESで復号化: " + des.decryptDes(encryptedStr));
		} catch (Exception e) {
		}
	}
}

 
実行結果は、このような感じになりました。

元の文字列: Hello!
MD5で暗号化: 952d2c56d0485958336747bcdd98590d
DESで暗号化: ec04d3dd171e3af9
DESで復号化: Hello!

 

このtest.javaは、その他のクラスと同じパッケージ内に作成してありますのでimport宣言は必要ありませんが、もしパッケージ外から利用する時には、import宣言が必要です。

 

標準で暗号化のためのAPIがあるなんて、Javaは便利ですね。C言語には無かったよなぁ。例外も丸投げですし、ライブラリ用としてはあんま使えないでしょうけど、暗号化処理の勉強になりました。

車輪の再発明も甚だしいですしね。作った後で分かったんですが、Byte型配列と16進数表記への変換するクラスなんかは、Apache CommonsのCodecパッケージにありましたし。

ただ、このBinaryHexConverter.javaで、Byte型配列からInt型数値への変換処理を入れているんですが、ここで結構ハマりました。

JavaではByte型からInt型への変換が、ただキャスト変換するだけではうまく行かないようなんです。 ソースコードを見て頂けると分かるんですが、Byte型をInt型へ変換する際には、「0xFF」とビットAND演算しないとならないようです。

つまりどういう事なのかというと、

byte a = (byte)0xC8 //10進数では200のはず
int b = (int)a;
Sytem.out.println(b);

の実行結果は、「200」になりません。「-56」という値になります。

それを、

byte a = (byte)0xC8 //10進数では200のはず
int b = a & 0xff;
Sytem.out.println(b);

とすると、実行結果は「200」になってくれます。

このあたりはネットで調べて初めて知ったのですが、最初はただ単にキャスト変換してうまく行かず、悩みまくってしまいました。

とりあえず「0xFF」でビット演算すればOKという事は分かったのですが、なぜそれをするとうまく行くのか?それが分からず、気になってしまい。いろいろと調べてみました。

調べた内容は長くなってしまうので、次回のエントリーでお話ししたいと思います。

No responses yet

コメントをどうぞ