Java (JCE) で AES 暗号化するときの PKCS#5 の実態は PKCS#7 なのか?

結論

JCE (Java Cryptography Extension) の AES/CBC/PKCS5Padding のパディングは、実際には PKCS#7 でパディングしている。
(少なくとも Oracle Java8_141 環境下では)

経緯

Java で AES 暗号化を扱う機会があり、ググっていたところ AES/CBC/PKCS5Padding がデフォルトサポートで使いよい。というところまで行ったところで、
「AES ってブロック長 128bit(16byte) だから 8byte までしかパディングできない PKCS#5 だとパディングできないんじゃ?」
と疑問に思ったわけです。

PKCS#7 であれば 16byte ブロックでも問題なくパディングできるので、本来なら AES では PKCS7Padding であるべきなのにデフォルトサポートはしていない。
PKCS#7 は PKCS#5 のスーパーセットなので、JCE の PKCS5Padding が実際には PKCS#7 でパディングしているなら「名が体を表していないだけ」ということになる。

そこらへんを書いた話はないものかと、java pkcs5 pksc7 とかでググっても「PKCS#5と#7は同じ」とか「互換性がある」とかで残念ながらはっきりしたことはわからなかった。

Java 同士でやりとりする分にはいいけど、言語を跨いでやりとりするときに、ここらがはっきりしていないとトラブルよなぁ…。。

ということで、 JCE の PKCS5Padding は PKCS#7 の動作をしているのかどうかというところを検証してみた。

検証

以下の環境で検証を実施。

C:\Windows\System32>java -version
java version "1.8.0_141"
Java(TM) SE Runtime Environment (build 1.8.0_141-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.141-b15, mixed mode)
検証コード

次の 2 パターンでの暗号化結果を比較し、等しければ OK。

  • JCE の PKCS5 パディングで暗号化した場合 (AES/CBC/PKCS5Padding)
  • 自前で PKCS7 パディングしたバイト列を暗号化した場合 (AES/CBC/NoPadding)

平文長(元データ長)は 1 〜 16 byte として、それぞれで比較する。

import java.security.Key;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESPaddingTest {
    public static void main(String[] args) {
        try {
            // 元データ長が 1 〜 15 の場合
            for (int i = 1; i < 15; i++) {
                // 元データ
                byte[] base_sequence = new byte[i];
                // 自前パディングデータ
                byte[] padded_sequence = new byte[16];

                // PKCS7 で自前パディング
                byte pad = (byte) (padded_sequence.length - i);
                for (int j = i; j < padded_sequence.length; j++) {
                    padded_sequence[j] = pad;
                }

                boolean result = compare(base_sequence, padded_sequence);
                System.out.printf("[Source length %2d] %s%n", i, (result ? "OK (Same result)" : "Fail (Some difference"));
            }

            // 元データ長が 16 の場合(16byte時だけ分離してるのがちとダサい)
            byte[] base_sequence = new byte[16];
            byte[] padded_sequence = new byte[32];
            // PKCS7 で自前パディング
            for (int j = 16; j < padded_sequence.length; j++) {
                padded_sequence[j] = 16;
            }
            boolean result = compare(base_sequence, padded_sequence);
            System.out.printf("[Source length %2d] %s%n", 16, (result ? "OK (Same result)" : "Fail (Some difference"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 未パディングのバイト列と自前パディングのバイト列の暗号化後の結果が等しいかチェックする。
     * @param base_sequence 未パディングのバイト列
     * @param self_padded_sequence 自前パディングのバイト列
     * @return true 等しい。false 差異あり。
     * @throws Exception 
     */
    private static boolean compare(byte[] base_sequence, byte[] self_padded_sequence) throws Exception {
        byte[] key_raw = new byte[16];  // all 0
        byte[] iv_raw = new byte[16];   // all 0

        Key key = new SecretKeySpec(key_raw, "AES");
        IvParameterSpec iv = new IvParameterSpec(iv_raw);

        // Oracle JCE Provider で PKCS5Padding を指定した場合
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] base_enc = cipher.doFinal(base_sequence);

        // 自前パディングした場合(パディング済みなのでNoPadding)
        cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, key, iv);
        byte[] padded_enc = cipher.doFinal(self_padded_sequence);

        return Arrays.equals(base_enc, padded_enc);
    }
}

※上のコードは NYSL Version 0.9982 扱いです。

検証結果

暗号化されたバイト列を比較した結果、以下の通り全部同じだったので、JCEPKCS#5 パディングは実際には PKCS#7 パディングでした。ということに。

[Source length  1] OK (Same result)
[Source length  2] OK (Same result)
[Source length  3] OK (Same result)
[Source length  4] OK (Same result)
[Source length  5] OK (Same result)
[Source length  6] OK (Same result)
[Source length  7] OK (Same result)
[Source length  8] OK (Same result)
[Source length  9] OK (Same result)
[Source length 10] OK (Same result)
[Source length 11] OK (Same result)
[Source length 12] OK (Same result)
[Source length 13] OK (Same result)
[Source length 14] OK (Same result)
[Source length 16] OK (Same result)