Javaで文字列の暗号化をする処理が必要になったので、ライブラリクラスを作ってみました。
とりあえず、非可逆的な暗号化方式としてMD5を。可逆的な暗号化方式としてDESを選択。(※可逆的な暗号化というのは、暗号化された文字列を元の文字列に復号化できる方式。非可逆的な暗号化というのは、もう元には復号化できない方式です。)
引数として渡された文字列を、MD5やDESで暗号化してバイナリ配列を作り、それを16進数表記の文字列として返す。というメソッドを実装しました。
Javaには標準で暗号化のための便利なAPIが用意されているんですね。作るのも簡単です。
また、作成にあたってはJavaのAPIリファレンスの他、この辺りのHPも参考にさせて頂きました。
- TechScore http://www.techscore.com/tech/J2SE/JCE/2.html
- トラスト・ソフトウェア・システム http://www.trustss.co.jp/Java/JEncrypt100.html
まずは、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という事は分かったのですが、なぜそれをするとうまく行くのか?それが分からず、気になってしまい。いろいろと調べてみました。
調べた内容は長くなってしまうので、次回のエントリーでお話ししたいと思います。