2012年1月30日月曜日

java : 鍵の長さ256bitのAESを使う

javax.crypto.Cipherを使ってAESの暗号/複合化をするにあたり、デフォルトのままだとAESの鍵の長さは128bitしか使えません。 256bitの鍵を使うとInvalidKeyExceptionで怒られてしまいます。

java.security.InvalidKeyException: Invalid AES key length: 32 bytes

256bitの鍵を使うにはポリシーファイルが必要になります。

java7の場合は下の方にある「Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 7」をダウンロード。 ファイル名は「UnlimitedJCEPolicyJDK7.zip」でした。 これを解凍すると「local_policy.jar」と「US_export_policy.jar」が出てきます。 jreのフォルダ(jre7\lib\security)に同じ名前のファイルがあるので置き換えます。

これで256bitの鍵が使えるようになります。 あとはこういうサイトを参考にしてコーディングすればいいんじゃないですかね?

それを見て適当にコーディングしたらこうなりました。

package aescrypttest;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesCryptTest
{
    private final String CIPHER_ALGORITHM = "AES";
    private final String CIPHER_TRANSFORMATION = CIPHER_ALGORITHM + "/CBC/PKCS5Padding";
    private Cipher _encrypt;
    private Cipher _decrypter;
    private IvParameterSpec _iv;

    public static void main(String[] args)
    {
        new AesCryptTest().run();
    }

    public void run()
    {
        String txt = "文字列漢字表現な何か?";
        String password = "test_phone_number";

        Key key = createKey(password, 256);
        initializeCipher(key);
        password = null; // もっとちゃんと破棄すべき?

        byte[] encrypted = encrypt(txt.getBytes() );
        byte[] decrypted = decrypt(encrypted );
        System.out.println(new String(decrypted) );
    }

    // passwordをシードにしてbitNumサイズのバイト列を作り、鍵にする。
    // 本番(何の?)に耐えられるかは不明。
    public Key createKey(String password, int bitNum)
    {
        SecureRandom random = new SecureRandom(password.getBytes() );
        byte buff[] = new byte[bitNum >> 3];
        random.nextBytes(buff);
        return new SecretKeySpec(buff, CIPHER_ALGORITHM);
    }

    public void initializeCipher(Key key)
    {
        try
        {
            _encrypt = Cipher.getInstance(CIPHER_TRANSFORMATION);
            _encrypt.init(Cipher.ENCRYPT_MODE, key);
            _iv = new IvParameterSpec(_encrypt.getIV());

            _decrypter = Cipher.getInstance(CIPHER_TRANSFORMATION);
            _decrypter.init(Cipher.DECRYPT_MODE, key, _iv);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
        }
    }

    public byte[] encrypt(byte[] src)
    {
        try
        {
            return _encrypt.doFinal(src);
        }
        catch (Exception exc)
        {
            exc.printStackTrace();
            return null;
        }
    }

    public byte[] decrypt(byte[] src)
    {
        try
        {
            return _decrypter.doFinal(src);
        }
        catch (Exception exc)
        {
            exc.printStackTrace();
            return null;
        }
    }
}

暗号/復号のコードはちょっとした順番とかタイミングとかで台無しになりますが、このコードがどのくらい大丈夫なのかは不明です。 まともに使えるコードを書くにはもうちょっと勉強しないと...

このコードは、例えば「アプリケーション起動時にパスワードをもらって、あとはそれをキーに読んだり書いたり読んだり書いたり」ってのを想定しています。 でもちょっと検索したらそれだけだと弱そうなのがすぐ分かりますよね。

でもまぁ、用途次第かな?