共通鍵暗号方式のAESによる暗号化と復号化
AESとは、共通鍵暗号方式の暗号アルゴリズムの1つです。AESは、Advanced Encryption Standardの略です。DESの安全性が低下していったため、代替のために開発されました。Javaでは、いろいろな暗号アルゴリズムが利用できます。ここでは、AESを利用して暗号化、復号(復号化)を行います。
読み方
- AES
- えーいーえす
- Advanced Exception Standard
- あどばんすど えんくりぷしょん すたんだーど
目次
概要
暗号は、いろいろなところで利用されています。たとえば、ウェブサイトのログインページは、たいていの場合、SSL/TLSと呼ばれるプロトコルを利用して、暗号化通信を行っています。ウェブサイトでは、顧客個人情報を預かる場合に、情報によっては、暗号化するなどして、運用者にデータを見れない、改ざんできないように守っています。データが漏れた場合にも、平文よりは、マシでしょう(鍵とアルゴリズムが漏れたら、無意味ですが)。
TLSでAES-CBCの128ビット/256ビットが利用されています。ほかにもZIPやRARで、暗号化した圧縮ファイルを作成するときに256ビットAESが利用されています。
AES以外の暗号アルゴリズムも利用できます。使用可能な暗号アルゴリズムを調べるをご参照ください。 このプログラムは、鍵長は、128ビットです。
暗号化とは、一定のアルゴリズムで平文を暗号文に置き換えることです。復号(復号化)とは、暗号文を平文に戻すことです。
平文 -> 暗号化 -> 暗号文 平文 <- 復号 <- 暗号文
流れ
プログラムの流れは、以下の通りです。
- 鍵を生成する
- IV(Initial Vector, 初期化ベクトル)を生成する
- データを暗号化する
- 暗号化したデータを復号化する
PKCS5Paddingなどが見つからない
ブロック暗号方式は、データサイズがブロックサイズの倍数でないと暗号化できません。そのため、ブロックサイズに合わない最後のブロックをパディングで調整する必要があります。
パディングのためのPKCS5Paddingがあるはずなのですが、NOPADDINGしか見つからず、今回は、データサイズが必ずあっている前提でコードを書いています。
鍵の生成
暗号では、暗号鍵が利用されます。鍵は、公開してはいけません。鍵の公開範囲は、最小限でなければなりません。 鍵の生成は、以下の通りです。
KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom random = new SecureRandom(); keygen.init(128, random); byte[] key = keygen.generateKey();
サンプルのコードでは、異なる鍵が毎回生成されます。暗号文を復号するために、暗号鍵が必要になるため、安全な方法で保存しておく必要があります。
初期化ベクトルの生成
CBCモードでは、初期化ベクトル(初期ベクトル)が必要になります。最初のブロックの暗号化で、初期化ベクトルが利用されます。 以下のコードで、初期化ベクトルが作成できます。
SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); byte[]_iv = new byte[16]; random.nextBytes(iv);
サンプルのコードでは、異なる初期化ベクトルが毎回生成されます。暗号文を復号するために、初期化ベクトル号鍵が必要になるため、安全な方法で保存しておく必要があります。
暗号アルゴリズムの選択
暗号アルゴリズムの指定は、ここでやっています。
Cipher cipher = Cipher.getInstance(ALGORITHM);
実際には、
Cipher cipher = Cipher.getInstance("AES_128/CBC/NOPADDING");
となっています。
AES_128/CBC/NOPADDING
の意味ですが、以下の意味になります。
- AES_128
- 暗号アルゴリズムとビット長です。AESで128ビットということになります。
- CBC
- 暗号利用モードの1つです。Cipher Block Chaining Mode です。
- NOPADDING
- パディングに関するオプションです。データをパディングしません。
暗号処理
暗号の処理を実際に行っているのはこのコードになります。 Cipher を初期化 (このコードでは、cipher.init)するときの、第一引数で、暗号化するか(Cipher.ENCRYPT_MODE)、復号化(Cipher.DECRYPT_MODE)するか、指定します。今回のデータは、1回で暗号化できるので、doFinal()しか呼び出していません。
SecretKeySpec skey = new SecretKeySpec(key, CIPHER); IvParameterSpec ivp = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(ALGORITHM); int m = (mode == MODE.ENCRYPT) ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; cipher.init(m, skey, ivp); output = cipher.doFinal(input);
AES1
データサイズは、16バイト(128ビット)の倍数でなければなりません。
ソースコード AES1.java
/* * AES1.java * Copyright (C) 2015 kaoru <kaoru@localhost> */ import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.spec.KeySpec; import java.security.SecureRandom; import javax.crypto.spec.IvParameterSpec; import java.util.Base64; public class AES1 { enum MODE { ENCRYPT, DECRYPT }; public static SecretKey ENCRYPT_KEY; public static int KEY_SIZE = 128; public static byte[] ENCRYPT_IV; public static String ALGORITHM = "AES_128/CBC/NOPADDING"; public static String CIPHER = "AES"; public static String char_code = "UTF-8"; public static String encrypt (String plaintext) throws Exception { byte[] cipher_byte = crypt(plaintext.getBytes(), MODE.ENCRYPT); String b64 = Base64.getEncoder().encodeToString(cipher_byte); return b64; } public static String decrypt (String text64) throws Exception { byte[] cipher_byte = Base64.getDecoder().decode(text64.getBytes() ); byte[] plain_byte = crypt(cipher_byte, MODE.DECRYPT); String plaintext = new String(plain_byte, char_code); return plaintext; } public static byte[] crypt(byte[] input, MODE mode) throws Exception { byte[] output; try { byte[] key = ENCRYPT_KEY.getEncoded(); byte[] iv = ENCRYPT_IV; SecretKeySpec skey = new SecretKeySpec(key, CIPHER); IvParameterSpec ivp = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(ALGORITHM); int m = (mode == MODE.ENCRYPT) ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE; cipher.init(m, skey, ivp); output = cipher.doFinal(input); } catch (Exception e) { e.printStackTrace(); throw new Exception("Can not crypt"); } return output; } public static void makeKey () throws Exception { try { KeyGenerator keygen = KeyGenerator.getInstance(CIPHER); SecureRandom random = new SecureRandom(); keygen.init(KEY_SIZE, random); ENCRYPT_KEY = keygen.generateKey(); } catch (Exception e) { throw new Exception("Can not make key", e); } } public static void makeIv () throws Exception { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); ENCRYPT_IV = new byte[16]; random.nextBytes(ENCRYPT_IV); } public static void main(String[] args) { String text = "HogehogeHogehoge"; try { makeKey(); makeIv(); String cipher_text = encrypt(text); String plain_text = decrypt(cipher_text); System.out.println(cipher_text); System.out.println(plain_text); } catch (Exception e) { e.printStackTrace(); } } }
コンパイル
javac AES1.java
実行例
$ java AES1 6TLZ+5HF/9CIbsWa/0mo6g== HogehogeHogehoge
エラーの例
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
平文のデータが16バイトの倍数でない場合、エラーになります。
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1016) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:984) at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824) at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436) at javax.crypto.Cipher.doFinal(Cipher.java:2165)
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
IV(初期化ベクトルのサイズは、16バイトでなければなりません。
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long at com.sun.crypto.provider.CipherCore.init(CipherCore.java:516) at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:339) at javax.crypto.Cipher.implInit(Cipher.java:806) at javax.crypto.Cipher.chooseProvider(Cipher.java:864) at javax.crypto.Cipher.init(Cipher.java:1396) at javax.crypto.Cipher.init(Cipher.java:1327)
java.security.InvalidKeyException: The key must be 128 bits
鍵長は、128ビットでない場合、エラーになります。
java.security.InvalidKeyException: The key must be 128 bits at com.sun.crypto.provider.AESCipher.checkKeySize(AESCipher.java:158) at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:338) at javax.crypto.Cipher.implInit(Cipher.java:806) at javax.crypto.Cipher.chooseProvider(Cipher.java:864) at javax.crypto.Cipher.init(Cipher.java:1396) at javax.crypto.Cipher.init(Cipher.java:1327)
関連項目
- 使用可能な暗号アルゴリズムを調べる
- 共通鍵暗号方式のAESによる暗号化と復号化
- JavaでSHA256を計算する
- JavaでHMAC-SHA256を計算する
- 暗号
ツイート