CLOVER🍀

That was when it all began.

JavaでAESECBCBCを䜿う

これは、なにをしたくお曞いたもの

こちらの゚ントリを曞いおいお、「JavaでAESを䜿う時のコヌドを党然芚えおないな」ず思いたしお。

MessageDigestに"SHA"とか、Cipherに"AES"とだけ指定した場合、どうなるの? - CLOVER🍀

メモしおおこうかな、ず。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.9.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-60-generic", arch: "amd64", family: "unix"

JavaでAESを䜿う

JavaでAESを扱うには、Cipherクラスを䞻ずしお䜿いたす。

Cipher (Java SE 11 & JDK 11 )

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / Cipherクラス

あず、こちらも。

KeyGenerator (Java SE 11 & JDK 11 )

SecretKeySpec (Java SE 11 & JDK 11 )

IvParameterSpec (Java SE 11 & JDK 11 )

今回、アルゎリズム、モヌド、パディングは以䞋の2぀を䜿っおプログラムを曞くこずにしたしょう。

  • AES/ECB/PKCS5Padding
  • AES/CBC/PKCS5Padding

鍵の長さは最初は128ビットで行い、最埌に192ビット、256ビットたで扱いたす。

パディングなしの堎合ず、他のモヌドは今回は考えないこずにしたす。

Javaセキュリティ暙準アルゎリズム名 / Cipherアルゎリズム名

JDKプロバむダ・ドキュメント / SunJCEプロバむダ

この前提で、テストコヌドで䜿い方をメモしおいこうかな、ず。

テストコヌドは、䜿甚するクラスを倉え぀぀、暗号化した倀を埩号できるずころたでを確認したす。

準備

テストコヌド甚にJUnit 5ずAssertJを䟝存関係に远加し、Maven Surefire Pluginの蚭定を行いたす。

    <dependencies>
        <dependency>
          <groupId>org.assertj</groupId>
          <artifactId>assertj-core</artifactId>
          <version>3.18.1</version>
          <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.7.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
        </plugins>
    </build>

テストコヌドの雛圢は、こちら。
src/test/java/org/littlewings/aes/AesEncryptDecryptTest.java

package org.littlewings.aes;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class AesEncryptDecryptTest {

    // ここに、テストを曞く
}

では、曞いおいきたしょう。

KeyGeneratorを䜿っお秘密鍵を䜜成する

AESは共通鍵暗号アルゎリズムなわけですが、秘密鍵の䜜成をKeyGeneratorに任せるこずができたす。

KeyGenerator (Java SE 11 & JDK 11 )

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / KeyGeneratorクラス

AES/ECB/PKCS5Paddingを䜿う堎合。

    @Test
    public void ecbUsingKeyGenerator() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // SecretKey
        int keySizeAsBit = 128;  // 128 bit
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(keySizeAsBit);
        SecretKey secretKey = keyGenerator.generateKey();

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        encryptor.init(Cipher.ENCRYPT_MODE, secretKey);

        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");

        decryptor.init(Cipher.DECRYPT_MODE, secretKey);

        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

暗号化察象の倀は、いずれのテストケヌスでもこの倀にしたす。

        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

KeyGeneratorを䜿甚した、秘密鍵の生成はこちら。

        // SecretKey
        int keySizeAsBit = 128;  // 128 bit
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(keySizeAsBit);
        SecretKey secretKey = keyGenerator.generateKey();

KeyGenerator#getInstanceの匕数にどのアルゎリズム甚のキヌを䜜るのかを指定する必芁があるのですが、AESずだけ指定したす。
この郚分に関しおは、モヌドやパディングは関係ありたせん。

Javaセキュリティ暙準アルゎリズム名 / KeyGeneratorアルゎリズム

KeyGenerator#initでは、䜜成する秘密鍵のサむズを指定したす。今回は鍵のサむズは128ビットなので、128ず指定したす。

最埌にKeyGenerator#generateKeyを呌び出すず、SecretKey、すなわち秘密鍵が䜜成されたす。

あずは、暗号化したす。

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        encryptor.init(Cipher.ENCRYPT_MODE, secretKey);

        byte[] encrypted = encryptor.doFinal(value);

埩号の際には、同じ秘密鍵を䜿いたす。

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");

        decryptor.init(Cipher.DECRYPT_MODE, secretKey);

        byte[] decrypted = decryptor.doFinal(encrypted);

AES/CBC/PKCS5Paddingの堎合はこちら。

    @Test
    public void cbcUsingKeyGeneratorNoProvideIv() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // SecretKey
        int keySizeAsBit = 128;  // 128 bit
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(keySizeAsBit);
        SecretKey secretKey = keyGenerator.generateKey();

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        encryptor.init(Cipher.ENCRYPT_MODE, secretKey);
        byte[] ivBytes = encryptor.getIV();  // auto generate
        byte[] encrypted = encryptor.doFinal(value);

        assertThat(ivBytes).hasSize(16);  // block size
        assertThat(ivBytes).hasSize(encryptor.getBlockSize());  // block size

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, secretKey, iv);  // iv required
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

CBCの堎合、IV初期化ベクトルが必芁になりたす。

Cipher#init時にIVを明瀺的に䞎えない堎合はランダムに䜜成されたすが、このIVは埩号時に必芁になりたす。
よっお、生成されたIVを取埗する必芁がありたすCipher#getIV。

        byte[] ivBytes = encryptor.getIV();  // auto generate

IVを䜜っおいるのは、ここですね。

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java#L397-L405

埗られたIVバむト配列は、IvParameterSpecのむンスタンス生成に䜿いたす。

IvParameterSpec (Java SE 11 & JDK 11 )

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(ivBytes);

このIvParameterSpecをCipher#init時に䞎え、埩号を行いたす。暗号化に䜿甚したIVなしでは、埩号できたせん。

        decryptor.init(Cipher.DECRYPT_MODE, secretKey, iv);  // iv required
        byte[] decrypted = decryptor.doFinal(encrypted);

KeyGeneratorに関しおは、こんな感じで。

ちなみに、KeyGeneratorが実際に秘密鍵を䜜成を䟝頌するのはAESKeyGeneratorなのですが、こちらはSecureRandom#nextBytesを
䜿っおいるだけだったりしたす。

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/AESKeyGenerator.java#L105-L116

SecretKeySpecを䜿っお秘密鍵を䜜る

次は、ScretKeySpecを䜿っお秘密鍵を䜜っおみたす。先ほどはKeyGeneratorに秘密鍵の䜜成を完党に任せたしたが、
こちらは秘密鍵のデヌタを自分で甚意するこずになりたす。

SecretKeySpec (Java SE 11 & JDK 11 )

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / KeySpecむンタフェヌス

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / KeySpecサブむンタフェヌス

たずはAES/ECB/PKCS5Paddingから。

    @Test
    public void ecbUseUserDefinedSecretKey() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 16;  // 128 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey);
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey);
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

秘密鍵ずなる16バむト128ビット分のバむト配列が必芁なのですが、今回はSecureRandomで䜜るこずにしたした。

        // User Defined SecretKey
        int keySize = 16;  // 128 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

たあ、この方法を遞んだ時点で先ほどのKeyGeneratorずやっおいるこずは倉わらないのですが、鍵のデヌタを自分で甚意した䜓で
今回は曞いおいたす。

このバむト配列は、SecretKeySpecのコンストラクタに匕き枡したす。2぀目の匕数にはAESず指定したす。こちらもKeyGeneratorの時ず
同様に、モヌドやパディングに぀いおは関係なくAESずだけ指定すればOKです。

        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

あずは、KeyGeneratorを䜿った堎合ず同じです。

AES/CBC/PKCS5Paddingの堎合は、こちら。

    @Test
    public void cbcUseUserDefinedSecretKeyNoProvideIv() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 16;  // 128 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey);
        byte[] ivBytes = encryptor.getIV();  // auto generate
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec iv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey, iv);  // iv required
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

KeyGeneratorを䜿っおいた郚分がSecretKeySpecを䜿う内容に曞き換わっおいるだけなので、特段倉わったずころは
ありたせん。

なお、SecretKeySpec䜜成時に䜿ったバむト配列はSecretKey#getEncodedで取り出せたすし、AESの鍵を
KeyGenerator#generateKeyで䜜った堎合に埗られるのもSecretKeySpecです。

    @Test
    public void secretKeySpecEncodedBytes() throws NoSuchAlgorithmException {
        // direct SecretKeySpec
        int keySize = 16;  // 128 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        SecretKey secretKey = new SecretKeySpec(secretKeyBytes, "AES");

        assertThat(secretKeyBytes).isEqualTo(secretKey.getEncoded());

        // KeyGenerator
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(keySize * 8);
        SecretKey secretKeyFromKeyGenerator = keyGenerator.generateKey();

        assertThat(secretKeyFromKeyGenerator).isInstanceOf(SecretKeySpec.class);
    }

なので、秘密鍵の䜜成だけKeyGeneratorで行っお、生成したバむト配列を埌から取り出すずいうのもできたす、ず。

IvParameterSpecを䜿っおIV初期化ベクトルを指定する

CBCのような、IV初期化ベクトルを必芁ずするモヌドの堎合の話です。ここたでは、暗号化する時にIVはCipherに
生成しおもらっおいたしたが、ここではIVを自分で甚意するこずにしたす。

䜿うのは、IvParameterSpecクラスです。

IvParameterSpec (Java SE 11 & JDK 11 )

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / AlgorithmParameterSpecむンタフェヌス

䜜成したコヌドはこちら。AES/CBC/PKCS5Paddingです。

    @Test
    public void cbcUseUserDefinedSecretKeyProvideIv() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 16;  // 128 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // Initial Vector
        int blockSize = 16;
        byte[] ivBytes = new byte[blockSize];
        random.nextBytes(ivBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");

        assertThat(encryptor.getBlockSize()).isEqualTo(blockSize);  // block-size = 16 byte

        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec encryptIv = new IvParameterSpec(ivBytes);

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey, encryptIv);  // using iv
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec decryptIv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey, decryptIv);  // using iv
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

IV甚のデヌタは、ブロックサむズ16バむト甚意したす。今回は、他に習っおSecureRandomで甚意したした。

        // Initial Vector
        int blockSize = 16;
        byte[] ivBytes = new byte[blockSize];
        random.nextBytes(ivBytes);

ブロックサむズは、Cipherのむンスタンスを䜜成した埌であればCipher#getBlockSizeで埗るこずができたす。

        assertThat(encryptor.getBlockSize()).isEqualTo(blockSize);  // block-size = 16 byte

AESのブロックサむズは、16バむトで固定です。

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java#L185

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/AESConstants.java#L40

IVずしお甚意したバむト配列は、IvParameterSpecのコンストラクタに枡しおむンスタンスを䜜成し、Cipher#initの匕数ずしお
䜿いたす。

        IvParameterSpec encryptIv = new IvParameterSpec(ivBytes);

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey, encryptIv);  // using iv
        byte[] encrypted = encryptor.doFinal(value);

埩号の時も、同じIVの倀を䜿いたす。

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec decryptIv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey, decryptIv);  // using iv
        byte[] decrypted = decryptor.doFinal(encrypted);

AESを扱う時に䜿いそうなクラスは、こんな感じではないでしょうか。

128ビット以倖の鍵を䜿う

AESは鍵の長さずしお、128ビット、192ビット、256ビットの3぀を利甚できたす。

Java 9より前は128ビット以倖の鍵を䜿う堎合は倉曎䜜業が必芁だったのですが、Java 9以降はデフォルトで䜿えるようになっおいたす。

Java暗号化アヌキテクチャ(JCA)リファレンス・ガむド / 暗号匷床の構成

$JAVA_HOME/conf/security/java.securityずいうファむルのcrypto.policyプロパティが、unlimitedになったからです。

$ grep ^crypto.policy /usr/lib/jvm/java-11-openjdk-amd64/conf/security/java.security
crypto.policy=unlimited

$JAVA_HOME/conf/security/java.securityファむル内の、crypto.policyのコメントを蚘茉しおおきたす。

#
# Cryptographic Jurisdiction Policy defaults
#
# Import and export control rules on cryptographic software vary from
# country to country.  By default, Java provides two different sets of
# cryptographic policy files[1]:
#
#     unlimited:  These policy files contain no restrictions on cryptographic
#                 strengths or algorithms
#
#     limited:    These policy files contain more restricted cryptographic
#                 strengths
#
# The default setting is determined by the value of the "crypto.policy"
# Security property below. If your country or usage requires the
# traditional restrictive policy, the "limited" Java cryptographic
# policy is still available and may be appropriate for your environment.
#
# If you have restrictions that do not fit either use case mentioned
# above, Java provides the capability to customize these policy files.
# The "crypto.policy" security property points to a subdirectory
# within <java-home>/conf/security/policy/ which can be customized.
# Please see the <java-home>/conf/security/policy/README.txt file or consult
# the Java Security Guide/JCA documentation for more information.
#
# YOU ARE ADVISED TO CONSULT YOUR EXPORT/IMPORT CONTROL COUNSEL OR ATTORNEY
# TO DETERMINE THE EXACT REQUIREMENTS.
#
# [1] Please note that the JCE for Java SE, including the JCE framework,
# cryptographic policy files, and standard JCE providers provided with
# the Java SE, have been reviewed and approved for export as mass market
# encryption item by the US Bureau of Industry and Security.
#
# Note: This property is currently used by the JDK Reference implementation.
# It is not guaranteed to be examined and used by other implementations.
#
crypto.policy=unlimited

Java 9より前は、こうでした。

Oracle Java JDK 9より前は、Oracle実装によっお蚱可されおいるデフォルト暗号匷床は、匷力だが制限付きでした(たずえば、128ビットに制限されたAESキヌ)。この制限をなくすには、管理者が、無制限匷床の管蜄ポリシヌ・ファむルのバンドルを別にダりンロヌドしおむンストヌルしたす。

では、どうしお制限されおいたかずいうず、これです。

これたでどおり、管理者およびナヌザヌは、自分の地理的な堎所に応じたすべおの茞入/茞出ガむドラむンに埓う必芁がありたす。

これを理解するにはEARExport Administration Regulationsずいう米囜茞出芏則、その䞭の品目ず囜のマッピングを読み解いおいく
必芁があるのですが 。

EAR超入門‐米囜の茞出芏制を孊がう‐䞀般財団法人安党保障貿易情報センタヌ 2019幎床版

Commerce Control List Overview and the Country Chart Supplement No. 1 to Part 738 page

Commerce Control List Supplement No. 1 to Part 774

途䞭で諊めたした 。日本ではOKなはずなので、今回はそのたた䜿っおみたす。

秘密鍵を192ビットにする

秘密鍵の長さを倉えるずいっおも、鍵ずしお甚意するデヌタの長さを倉えればよいのです。たずは192ビットの鍵にしたす。

AES/ECB/PKCS5Paddingの堎合。秘密鍵は自分で甚意する圢にしたした。

    @Test
    public void ecbUsing192BitSecretKey() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 24;  // 192 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey);
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey);
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

今回は、SecureRandomで甚意する鍵を24バむト192ビットにしおいたす。

        // User Defined SecretKey
        int keySize = 24;  // 192 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

ポむントはここだけです。

AES/CBC/PKCS5Paddingの堎合。IVは自分で甚意しおいたす。

    @Test
    public void cbcUsing192BitSecretKey() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 24;  // 192 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // Initial Vector
        int blockSize = 16;
        byte[] ivBytes = new byte[blockSize];
        random.nextBytes(ivBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec encryptIv = new IvParameterSpec(ivBytes);

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey, encryptIv);  // using iv
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec decryptIv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey, decryptIv);  // using iv
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }
秘密鍵を256ビットにする

秘密鍵を256ビットにしおみたす。甚意する鍵のデヌタのサむズが倉わるだけなので、コヌドを茉せるだけにしたす 。

AES/ECB/PKCS5Paddingの堎合。

    @Test
    public void ecbUsing256BitSecretKey() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 32;  // 256 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey);
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey);
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }

AES/CBC/PKCS5Paddingの堎合。

    @Test
    public void cbcUsing256BitSecretKey() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        // target
        byte[] value = "こんにちは、䞖界".getBytes(StandardCharsets.UTF_8);

        // User Defined SecretKey
        int keySize = 32;  // 256 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // Initial Vector
        int blockSize = 16;
        byte[] ivBytes = new byte[blockSize];
        random.nextBytes(ivBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec encryptIv = new IvParameterSpec(ivBytes);

        encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey, encryptIv);  // using iv
        byte[] encrypted = encryptor.doFinal(value);

        // decrypt
        Cipher decryptor = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKey decryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");
        IvParameterSpec decryptIv = new IvParameterSpec(ivBytes);

        decryptor.init(Cipher.DECRYPT_MODE, decryptSecretKey, decryptIv);  // using iv
        byte[] decrypted = decryptor.doFinal(encrypted);

        // assertion
        assertThat(new String(decrypted, StandardCharsets.UTF_8)).isEqualTo("こんにちは、䞖界");
    }
倉な鍵の長さにしおみる

こういうふうに単玔に動かしおいるずやや䞍安になるので、鍵のサむズをサポヌトされおいないものにするずどうなるか、
確認しおみたした。

ECBで鍵の長さを64バむト512ビットにしおみたす。

    @Test
    public void invalidKeyLength() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // User Defined SecretKey
        int keySize = 64;  // 512 bit
        SecureRandom random = new SecureRandom();
        byte[] secretKeyBytes = new byte[keySize];
        random.nextBytes(secretKeyBytes);

        // encrypt
        Cipher encryptor = Cipher.getInstance("AES/ECB/PKCS5Padding");
        SecretKey encryptSecretKey = new SecretKeySpec(secretKeyBytes, "AES");

        assertThatThrownBy(() -> encryptor.init(Cipher.ENCRYPT_MODE, encryptSecretKey))
                .isInstanceOf(InvalidKeyException.class)
                .hasMessage("Invalid AES key length: 64 bytes");
    }

するず、InvalidKeyExceptionがスロヌされたす。

鍵の長さをチェックしおいるのは、ここですね。

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/AESCrypt.java#L89-L92

そしお、䜿える鍵の長さが定矩されおいるのは、ここです。

https://github.com/openjdk/jdk11u/blob/jdk-11.0.9.1%2B1/src/java.base/share/classes/com/sun/crypto/provider/AESConstants.java#L45

16バむト128ビット、24バむト192ビット、32バむト256ビットの3぀が定矩されおいたす。

たずめ

Javaで、AESECB、CBCのパディングありに限っおですがを扱うコヌドを曞いおみたした。

時々䜿ったりする時に完党に忘れおいるので、メモずしお。

AESの゜ヌスコヌドを読んだり、CipherたわりずいうかJCAに関するドキュメントを読むいい機䌚にもなりたした。