これは、なにをしたくて書いたもの?
こちらのエントリを書いていて、「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クラスを主として使います。
Java暗号化アーキテクチャ(JCA)リファレンス・ガイド / Cipherクラス
あと、こちらも。
KeyGenerator (Java SE 11 & JDK 11 )
SecretKeySpec (Java SE 11 & JDK 11 )
IvParameterSpec (Java SE 11 & JDK 11 )
今回、アルゴリズム、モード、パディングは以下の2つを使ってプログラムを書くことにしましょう。
AES/ECB/PKCS5PaddingAES/CBC/PKCS5Padding
鍵の長さは最初は128ビットで行い、最後に192ビット、256ビットまで扱います。
パディングなしの場合と、他のモードは今回は考えないことにします。
Javaセキュリティ標準アルゴリズム名 / Cipherアルゴリズム名
この前提で、テストコードで使い方をメモしていこうかな、と。
テストコードは、使用するクラスを変えつつ、暗号化した値を復号できるところまでを確認します。
準備
テストコード用に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を作っているのは、ここですね。
得られた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を
使っているだけだったりします。
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バイトで固定です。
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がスローされます。
鍵の長さをチェックしているのは、ここですね。
そして、使える鍵の長さが定義されているのは、ここです。
16バイト(128ビット)、24バイト(192ビット)、32バイト(256ビット)の3つが定義されています。
まとめ
Javaで、AES(ECB、CBCのパディングありに限ってですが)を扱うコードを書いてみました。
時々使ったりする時に完全に忘れているので、メモとして。