2012年1月30日月曜日

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

2017年11月追記)

この投稿の内容は古い情報となっています。最新のjava9だとデフォルトのままでもAESの鍵の長さに制限はありません。 このページのサンプルコードもデフォルトのまま実行することができました。

また、java8の場合は古いバージョンはこの投稿の通りにjceが必要になります。 新しいバージョンのjava8の場合、jceに当たるファイルはインストール済みのようです。 設定ファイルの書き換えのみで鍵の長さの制限を撤廃できるのだそうです。

もし鍵の長さに制限がかかっていたのなら、java.securityというファイルで『crypto.policy』という項目を検索してみてください。 『crypto.policy=limited』と書かれていたのなら limited を unlimitedに変更、項目自体が無い場合は『crypto.policy=unlimited』と追加してみてください。 この辺かな?

  • java9 jre : ~\jre-9.?.?\conf\security\java.security
  • java9 jdk : ~\jdk-9.?.?\conf\security\java.security
  • java8 jre : ~\jre1.8.?_?\lib\security\java.security
  • java8 jdk : ~\jdk1.8.?_?\jre\lib\security\java.security

ただし、日米以外の国は場合によっては暗号化技術が違法になることがあるようです。 そのような国向けの環境では関連ファイルがインストールされない事でしょう。 一通り試してみて動かない場合は法律のチェックなども必要になるかもしれません。

以下、以前の投稿内容です。 そちらについては特に修正していません。


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;
        }
    }
}

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

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

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