「共通鍵暗号方式のAESによる暗号化と復号化」の版間の差分

提供: Java入門
移動: 案内検索
 
行1: 行1:
AESとは、共通鍵暗号方式の暗号アルゴリズムの1つです。AESは、Advanced Encryption Standardの略です。DESの安全性が低下していったため、代替のために開発されました。Javaでは、いろいろな暗号アルゴリズムが利用できます。ここでは、AESを利用して暗号化、復号(復号化)を行います。
+
'''AES'''とは、共通鍵暗号方式の暗号アルゴリズムの1つです。AESは、'''Advanced Encryption Standard'''の略です。DESの安全性が低下していったため、代替のために開発されました。Javaでは、いろいろな暗号アルゴリズムが利用できます。ここでは、'''AES'''を利用して'''暗号化'''、'''復号'''(復号化)を行います。
  
 
'''読み方'''
 
'''読み方'''
行6: 行6:
 
__TOC__
 
__TOC__
 
== 概要 ==
 
== 概要 ==
暗号は、いろいろなところで利用されています。たとえば、ウェブサイトのログインページは、たいていの場合、SSL/TLSと呼ばれるプロトコルを利用して、暗号化通信を行っています。ウェブサイトでは、顧客個人情報を預かる場合に、情報によっては、暗号化するなどして、運用者にデータを見れない、改ざんできないように守っています。データが漏れた場合にも、平文よりは、マシでしょう(鍵とアルゴリズムが漏れたら、無意味ですが)。
+
'''暗号'''は、いろいろなところで利用されています。たとえば、ウェブサイトのログインページは、たいていの場合、'''SSL/TLS'''と呼ばれるプロトコルを利用して、暗号化通信を行っています。ウェブサイトでは、顧客個人情報を預かる場合に、情報によっては、暗号化するなどして、運用者にデータを見れない、改ざんできないように守っています。データが漏れた場合にも、平文よりは、マシでしょう(鍵とアルゴリズムが漏れたら、無意味ですが)。
  
 
TLSでAES-CBCの128ビット/256ビットが利用されています。ほかにもZIPやRARで、暗号化した圧縮ファイルを作成するときに256ビットAESが利用されています。
 
TLSでAES-CBCの128ビット/256ビットが利用されています。ほかにもZIPやRARで、暗号化した圧縮ファイルを作成するときに256ビットAESが利用されています。
行13: 行13:
 
このプログラムは、鍵長は、128ビットです。
 
このプログラムは、鍵長は、128ビットです。
  
'''暗号化'''とは、一定のアルゴリズムで'''平文'''を'''暗号文'''に置き換えることです。'''復号'''(復号化)とは、'''暗号文を'''平文'''に戻すことです。
+
'''暗号化'''とは、一定のアルゴリズムで'''平文'''を'''暗号文'''に置き換えることです。'''復号'''(復号化)とは、'''暗号文'''を'''平文'''に戻すことです。
  
 
  平文 -> 暗号化 -> 暗号文
 
  平文 -> 暗号化 -> 暗号文
行22: 行22:
 
# IV(Initial Vector, 初期化ベクトル)を生成する
 
# IV(Initial Vector, 初期化ベクトル)を生成する
 
# データを暗号化する
 
# データを暗号化する
# 暗号化したデータを復号化する
+
# 暗号化したデータを復号する
 
== PKCS5Paddingなどが見つからない ==
 
== PKCS5Paddingなどが見つからない ==
 
ブロック暗号方式は、データサイズがブロックサイズの倍数でないと暗号化できません。そのため、ブロックサイズに合わない最後のブロックをパディングで調整する必要があります。
 
ブロック暗号方式は、データサイズがブロックサイズの倍数でないと暗号化できません。そのため、ブロックサイズに合わない最後のブロックをパディングで調整する必要があります。
行66: 行66:
 
== 暗号処理 ==
 
== 暗号処理 ==
 
暗号の処理を実際に行っているのはこのコードになります。
 
暗号の処理を実際に行っているのはこのコードになります。
Cipher を初期化 (このコードでは、cipher.init)するときの、第一引数で、暗号化するか(Cipher.ENCRYPT_MODE)、復号化(Cipher.DECRYPT_MODE)するか、指定します。今回のデータは、1回で暗号化できるので、doFinal()しか呼び出していません。
+
Cipher を初期化 (このコードでは、cipher.init)するときの、第一引数で、暗号化するか(Cipher.ENCRYPT_MODE)、復号(Cipher.DECRYPT_MODE)するか、指定します。今回のデータは、1回で暗号化できるので、doFinal()しか呼び出していません。
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
SecretKeySpec skey = new SecretKeySpec(key, CIPHER);
 
SecretKeySpec skey = new SecretKeySpec(key, CIPHER);

2015年9月21日 (月) 23:22時点における最新版

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ビットです。

暗号化とは、一定のアルゴリズムで平文暗号文に置き換えることです。復号(復号化)とは、暗号文平文に戻すことです。

平文 -> 暗号化 -> 暗号文
平文 <- 復号   <- 暗号文

流れ

プログラムの流れは、以下の通りです。

  1. 鍵を生成する
  2. IV(Initial Vector, 初期化ベクトル)を生成する
  3. データを暗号化する
  4. 暗号化したデータを復号する

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)

関連項目