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

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

背景

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

ポリシーファイルを差し替えれば 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 フレームワークが初期化されると途中で切り替えることはできない点に注意してください
    Security.setProperty("crypto.policy", "unlimited");
    int length = Cipher.getMaxAllowedKeyLength("AES"); // => 2147483647
    
    Security.setProperty("crypto.policy", "limited");
    int length = Cipher.getMaxAllowedKeyLength("AES"); // => 128
    
  2. java.security ファイルで設定する

    • $JAVA_HOME/conf/security/java.security を事前に編集しておくこともできます。Java 8 とは少しディレクトリ階層が変わっている点に注意してください
    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 の変更点

ここまでであれば「早く Java 9 に行きたいなぁ :weary: 」で終わってしまうのですが、実はこの仕組みが Java 8 にも 8u151 から入っています。つまり、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 である点に注意
    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 までは次のようなディレクトリ階層でした
    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 フレームワークが初期化され、この時点でどちらのポリシーが使われるかが確定してしまうからです。

    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 に早く移行すべし。

参考情報