JCEでAES/CTRを試してみた

TLSの例を調べたついでにJCEで実装する方法を調べてみた。

CTRモードを使う時の最大の問題はIVをどう扱うかということだったけれど、JCEのCipherではinit()時くらいしかIVの指定する方法がないような気がする。この場合、複数のブロックを暗号化する場合にIVが変化するのかどうかが知りたくなるのだけれど、探した限りでは明確な記述が見当たらなかった。

というわけで実際にCTRモードで同じブロックを2回暗号化するプログラムを作って、出力が変化するかどうかを調べてみた*1

// IVを準備
byte[] iv = new byte[16];
Arrays.fill(iv, (byte)0);

// 平文を準備
byte[] clearBlock = new byte[16];
Arrays.fill(clearBlock, (byte)0);

// 鍵と暗号用オブジェクトの準備
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecretKey secretKey = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

// IVを最初の1回だけ設定
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] encryptedBlock1 = cipher.update(clearBlock);
byte[] encryptedBlock2 = cipher.update(clearBlock);

// 2ブロック分の暗号文を出力
System.out.println("block1: " + Arrays.toString(encryptedBlock1));
System.out.println("block2: " + Arrays.toString(encryptedBlock2));

プログラムの実行結果はこのようになった。block1とblock2が異なることから、Cipherの内部でIVが変化していることが分かった。

block1: [-55, -97, -16, -56, 20, 120, -50, -79, 77, -75, 110, 11, 126, 76, -14, -122]
block2: [-45, 53, 115, -128, 30, 69, 69, -49, 29, -105, 10, -116, -82, -69, 26, -83]

というわけで次にIVがどのように変化するのか調べてみた。おそらく値をインクリメントしているのだろうと当たりをつけて、手動で1ブロックごとにIVをインクリメントするプログラムを追加してみた。

// IVをブロックごとにインクリメントした場合
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
encryptedBlock1 = cipher.update(clearBlock);

// IVをインクリメントしてcipherに設定
iv[15]++;
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
encryptedBlock2 = cipher.update(clearBlock);

// 2ブロック分の暗号文を出力
// 先ほどと同じ結果であればCipherもiv[15]をインクリメントしていることがわかる。
System.out.println("block1: " + Arrays.toString(encryptedBlock1));
System.out.println("block2: " + Arrays.toString(encryptedBlock2));

先ほどと鍵が違うため値は異なるが、手動でIVをインクリメントした場合とCipherが内部でIVを変えた場合が同じ結果になっている。

block1: [3, -127, 47, -47, 86, -8, 122, -97, -3, -89, 80, 82, 97, 24, -115, 42]
block2: [107, 34, -44, 36, -76, -12, -34, -95, -41, 43, 39, -26, -106, 119, -117, 102]
block1: [3, -127, 47, -47, 86, -8, 122, -97, -3, -89, 80, 82, 97, 24, -115, 42]
block2: [107, 34, -44, 36, -76, -12, -34, -95, -41, 43, 39, -26, -106, 119, -117, 102]

結論としては、Cipherは1ブロックごとに最下位ビットの方からIVをインクリメントする*2ようだ。

ただ仕様として決まっているわけでもなさそうなので、実装によっては異なる動作をするのかもしれない*3。そう考えると1ブロックごとに自分でIVを設定した方が確実なのだろうけれど、それも無駄が多い気がするのでどうしたものだろう。Bouncycastle辺りにプロバイダーを固定してしまえば悩む必要もないのかな。

*1:SunのJCEのソースが見当たらないからしょうがなく試してみたのだけれど、よく考えたらOpenJDKのソースを見れば良いことに後で気がつきました。

*2:ネットワークバイトオーダーで128ビットの変数をインクリメントすると考えれば良いのかな。

*3:常識的にはないだろうけど。