Java 9時代のAES 256暗号

もうポリシーファイルは要りません。

大事なことなのでもう一度言います。もうポリシーファイルは要りません。

## 背景

Java では標準ライブラリの Cipher クラスを使えば、サードパーティのライブラリを使うことなく AES 暗号を扱うことができます。しかし、AES 暗号はアメリカの輸出規制の対象になっているらしく、標準では 128bit までの鍵しか扱えません。日本のようにこの輸出規制の対象にならない国では、別途 Oracle から無制限強度のポリシーファイルをダウンロードして、システムに上書きする必要があることは有名な話です。

* [JavaでAES256を使用できるようにする](https://qiita.com/mizuki_takahashi/items/cc26a7fd51aa04396e92)

ポリシーファイルを差し替えれば AES 256 暗号を使えるのでよいのですが、よいのですが、...正直いって非常に面倒です。このあたりの経緯を知らない人に説明するのも面倒ですし、JDK を更新するたびに差し替えるのも面倒です。面倒なだけならまだしも、忘れたり、ミスしたりするリスクもあります。

## Java 9 の変更点

Java 9 では、このポリシーファイルがあらかじめ JDK に同梱され、セキュリティプロパティ `crypto.policy` を変えるだけで制御できるようになりました。

* `unlimited` → 無制限強度（AES 256 も使用可）
* `limited` → 制限強度（従来通り AES 128 まで使用可）

セキュリティプロパティ `crypto.policy` は、次の 2 通りの方法で設定することができますが、Java 9 からは **デフォルトで** `unlimited` になっているため、実際には何もする必要がありません。

1. `Security.setProperty()` 呼び出しで設定する
    * プログラムから設定できるため、ポリシーファイル差し替えや事前の設定ファイル書き換えから解放されます。ただし、後述するように、一度 JCE フレームワークが初期化されると途中で切り替えることはできない点に注意してください

    ```java
    Security.setProperty("crypto.policy", "unlimited");
    int length = Cipher.getMaxAllowedKeyLength("AES"); // => 2147483647
    ```

    ```java
    Security.setProperty("crypto.policy", "limited");
    int length = Cipher.getMaxAllowedKeyLength("AES"); // => 128
    ```

2. `java.security` ファイルで設定する
    * `$JAVA_HOME/conf/security/java.security` を事前に編集しておくこともできます。Java 8 とは少しディレクトリ階層が変わっている点に注意してください

    ```shell-session:Java9
    $ cd /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/conf/security/  # macOSの例
    $ tree
    .
    ├── java.policy
    ├── java.security  # このファイルで設定
    ├── javaws.policy
    └── policy
        ├── README.txt
        ├── limited
        │   ├── default_US_export.policy
        │   ├── default_local.policy
        │   └── exempt_local.policy
        └── unlimited
            ├── default_US_export.policy
            └── default_local.policy

    $ grep "crypto.policy=" java.security
    crypto.policy=unlimited
    ```

## Java 8 の変更点

> ■ 2018-01-17 追記 (thx. @yehara)
> [8u161](http://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html#JDK-8170157) から Java 8 でもデフォルトが `unlimited` に変わりました。Java 8 環境でも最新の JDK に更新するだけで、AES 256暗号が使えるようになります :congratulations: 

ここまでであれば「早く Java 9 に行きたいなぁ :weary: 」で終わってしまうのですが、実はこの仕組みが Java 8 にも [8u151](http://www.oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html#JDK-8157561) から入っています。つまり、Java 8 ユーザーもセキュリティプロパティ `crypto.policy` を設定するだけで AES 256 暗号が使えるようになったのです。

ただし、Java 9 の仕様とは若干異なる部分もあるため、次の点に注意が必要です。

* デフォルト値は未指定（`limited` 相当）なので、明示的に `unlimited` に変更が必要
* 従来のディレクトリ階層を踏襲しており、Java 9 とは異なる

無制限強度の暗号化を有効にする方法は、次のいずれかです。

1. `Security.setProperty()` 呼び出しで設定する
    * Java 9 と同じ
2. `java.security` ファイルで設定する
    * Java 9 と異なり、`$JAVA_HOME/jre/lib/security/java.security` である点に注意

    ```shell-session:8u151
    $ cd /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/security/
    $ tree
    .
    ├── blacklist
    ├── blacklisted.certs
    ├── cacerts
    ├── java.policy
    ├── java.security  # このファイルで設定
    ├── policy
    │   ├── limited
    │   │   ├── US_export_policy.jar
    │   │   └── local_policy.jar
    │   └── unlimited
    │       ├── US_export_policy.jar
    │       └── local_policy.jar
    └── trusted.libraries

    $ grep "crypto.policy=" java.security                                           
    # (equivalent to crypto.policy=limited)
    #crypto.policy=unlimited
    ```
    * 参考までに、8u144 までは次のようなディレクトリ階層でした

    ```shell-session:8u144
    $ cd /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/security/
    $ tree 
    .
    ├── US_export_policy.jar  # このファイルを差し替え
    ├── blacklist
    ├── blacklisted.certs
    ├── cacerts
    ├── java.policy
    ├── java.security
    ├── local_policy.jar      # このファイルを差し替え
    └── trusted.libraries
    ```

3. ポリシーファイル `US_export_policy.jar`, `local_policy.jar ` で設定する
    * 運用を変えたくない以外のメリットはないと思いますが、従来通り `$JAVA_HOME/jre/lib/security/` 直下にポリシーファイルを配置して対応することもできます。ただし、この場合は `crypto.policy` が未指定（デフォルト値）であることが条件です

## 注意事項

`Security.setProperty()` を使ってプログラムで設定する 1. の方法ですが、一部のアプリケーションでは期待通りに動作しないため注意が必要です。ポイントは、 **JCEフレームワークの初期化より前に設定しなければならない** という点です。

例えば、次のコードは何の問題もないように見えますが、`length2` も 128 になってしまいます。というのも、1 回目に Cipher クラスを参照したタイミングで JCE フレームワークが初期化され、この時点でどちらのポリシーが使われるかが確定してしまうからです。

```java
    int length = Cipher.getMaxAllowedKeyLength("AES");  // => 128
    Security.setProperty("crypto.policy", "unlimited");
    int length2 = Cipher.getMaxAllowedKeyLength("AES"); // => 128 (!)
```

単純なコマンドラインツールを作る場合であればよいのですが、例えば Tomcat 上で動作する Web アプリケーションの場合は問題になりえます。HTTPS 通信を有効にしていると、自分のアプリケーションコードが実行されるよりも前に JCE フレームワークが初期化されてしまうからです。

なお、同じ Web アプリケーションでも、Spring Boot などで埋め込みコンテナを使っている場合には、エントリポイントとなる main メソッドをもったクラスがアプリケーション側にあるため、コンテナの初期化より早く自身のクラスでセキュリティプロパティを差し替えることができます。

## まとめ

この記事では、Java で AES 256 暗号を扱うときに必要だったポリシーファイルの差し替えが、Java 9 そして Java 8 からも不要になったことを紹介しました。

せっかくポリシーファイルの事前差し替えが不要になったのだから、`Security.setProperty()` を使う方法 1. の一択でいきたいと思うのが人情ですが、Java 8 では開発しているアプリケーションの種類によってはうまくいかないため注意してください。デフォルト値が `unlimited` になる Java 9 に早く移行すべし。

## 参考情報

* [Java 9 - Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files](http://mail.openjdk.java.net/pipermail/security-dev/2016-October/014943.html)
* [JDK 8u151 Update Release Notes](http://www.oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html)
* [JDK 8u161 Update Release Notes](http://www.oracle.com/technetwork/java/javase/8u161-relnotes-4021379.html)