暗号化・復号化

暗号化とは特定の人にのみにしかわからないようにデータを暗号にすることで、
復号化は、暗号化されたデータを暗号化する前の状態へ戻すことです。

暗号化

「com/cmps/spring/controller」に「EncryptController」を配置しましょう。

・EncryptController.java

package com.cmps.spring.controller;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

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

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class EncryptController {

    @GetMapping("/encrypt")
    public String Encrypt(Model model){
      try {
          // 暗号化する文字列
          String sampleText = "こんにちは";
          model.addAttribute("sampleText", sampleText);    

          // 1.暗号化・復号化で使用する鍵(4で使用)
          //  デフォルトの場合、128bit(=16byte)である必要がある。
          final String secretKey = "iS5KfTPfn4xLjYYy";

          // 2.初期ベクトル(5で使用)
          //  今回は暗号利用モードを「CBC(Cipher Block Chaining)」に指定しているため、初期ベクトルが必要。(※1)
          //  初期ベクトルは、暗号化にランダム性を持たせるための追加データ
          final String initVector = "ncfeKKfPYedi9hJs";

          // 3. 暗号化・複合を行うアルゴリズム(6で使用)
          final String algorithm = "AES/CBC/PKCS5Padding";

          // 4. SecretKeySpec の生成
          //     第一引数に 1 で用意した鍵のバイト配列、第二引数にアルゴリズムを文字列で指定。第二引数のアルゴリズムは単に `AES` と指定。
          //     1 の文字列を渡すと `java.security.InvalidKeyException` がスローされる
          final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES");


          // 5. IvParameterSpec の生成
          //   第一引数には 2 の文字列をバイト配列に変換して渡す。
          final IvParameterSpec ivSpec = new IvParameterSpec(initVector.getBytes(StandardCharsets.UTF_8));

          // 6. Cipher(暗号器)  の生成
          //   3 で用意したアルゴリズムを渡す
          final Cipher cipher = Cipher.getInstance(algorithm);

          // 7. Cipher を使用するために、初期化。
          //   第一引数には暗号化を行うか、複合を行うかを `int` で指定する
          //  今回は暗号化を想定して `Cipher.ENCRYPT_MODE` を指定しているが、
          //  複合したい場合は `Cipher.DECRYPT_MODE` を指定する
          cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);

          // 暗号化対象のデータ
          final byte[] source = sampleText.getBytes(StandardCharsets.UTF_8);

          // 8. 暗号化
          byte[] encryptBytes = cipher.doFinal(source);

          // 暗号化したテキストをModelへ格納
          model.addAttribute("encryptBytes", encryptBytes);

        } catch (Exception e) {
          e.getStackTrace();
          model.addAttribute("error", "暗号化に失敗しました");
        }

        // 画面に出力するViewを指定
        return "encrypt/encrypt";
   }
}

※1:CBCモードについては以下のサイトをご参照ください。
参照サイト:ブロック暗号

出力用のViewとして「src/main/resources/templates」フォルダ内に
「encrypt」フォルダを作成し、「encrypt.html」を作成します。

・encrypt.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">

<head>
	<meta charset="UTF-8">
	<title>暗号化・復号化</title>
</head>

<body>
        <h1>暗号化・復号化</h1>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${sampleText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${encryptBytes}"></span></p>
		</li>
		<li th:if="${error}">
			<p th:text="${error}" style="color:red;"></p>
		</li>
	</ul>
</body>

</html>

出力結果

・暗号化前テキスト:こんにちは

・暗号化後テキスト:[B@785b014

復号化

EncryptControllerのtry内に、以下のソースを追記し暗号化したテキストを復号しましょう。

・EncryptController.java

          // 復号化
          // 暗号化の 6 同様、Cipherオブジェクト(暗号器)を作成
          Cipher decrypter = Cipher.getInstance(algorithm);
  
          // 暗号器を復号モードへセット
          // keySpecは復号へ使う鍵、ivSpecは初期ベクトル
          decrypter.init(Cipher.DECRYPT_MODE,keySpec,ivSpec);

          // 暗号化されたデータ(encryptBytes)を平文に戻す
          String decryptText = new String(decrypter.doFinal(encryptBytes));

          // 復号したテキストをModelへ格納
          model.addAttribute("decryptText",decryptText);

復号した文字列を表示するため、encrypt.htmlにも追記しましょう
・encrypt.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">

<head>
	<meta charset="UTF-8">
	<title>暗号化・復号化</title>
</head>

<body>
        <h1>暗号化・復号化</h1>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${sampleText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${encryptBytes}"></span></p>
		</li>
        <!--以下を追記-->
		<li>
			<p>復号化後テキスト:<span th:text="${decryptText}"></span></p>
		</li>
        <!--ここまで-->
		<li th:if="${error}">
			<p th:text="${error}" style="color:red;"></p>
		</li>
	</ul>
</body>

</html>

暗号化する前の文字列「こんにちは」が表示されたのではないでしょうか?

暗号化アルゴリズム

暗号化において、暗号化アルゴリズムというものが用いられます。
暗号化アルゴリズムとは、データの暗号化処理における手順や規則のことです。

暗号化にはさまざまなアルゴリズムが存在します。
代表的な暗号化アルゴリズムには
・公開鍵暗号方式
・共通鍵暗号方式
・ハイブリッド暗号方式

などがあります。

公開鍵暗号方式は、暗号化と復号に使う鍵が別々の方式です。
暗号化にはデータ受信者が一般公開した公開鍵、復号にはデータ受信者だけが持つ秘密鍵が使われます。
そのため、非対称鍵(Asymmetric Key)方式とも呼ばれます。

共通鍵暗号方式は、送信者と受信者の両方が同じ鍵を共有してデータを暗号化・復号化します
公開鍵暗号方式より安全性は劣りますが、処理速度に優れています。

ハイブリッド暗号方式は、共通鍵と公開鍵の両方を組み合わせた暗号方式です。
送付したいデータを処理速度が速い共通鍵暗号方式で暗号化し、暗号化に使用した公開鍵を公開鍵暗号方式で暗号化します。
ハイブリッド暗号方式の代表例としては、HTTPS通信が挙げられます。

EncryptControllerの「4. SecretKeySpec の生成。」では、
バイト列から秘密鍵(SecretKey)を作成するSecretKeySpecの第2引数に「AES」というものが記載されています。

AES(Advanced Encryption Standard)とは共通鍵暗号方式アルゴリズムの一つで、
ある一定のデータ長で区切って、それを「ブロック」という単位で処理するブロック暗号です。
鍵の長さは128bit、192bit、256bitの3種があり、Javaでは128bit(16byte)がデフォルトとなっています。

SpringSecurity 暗号モジュール

SpringSecurityとは、Springベースのアプリケーションに、認証 (BASIC認証、OpenID認証など)、
認可 (権限によるアクセス許可、OAuth 2.0など)、その他多数のセキュリティ対策を可能にするフレームワークです。

その中のモジュール(プログラムの構成要素)に「Spring Security Crypto モジュール」というものがあります。
このモジュールは対称暗号化、キー生成、パスワードエンコーディングのサポートを提供します。

Spring Security Crypto モジュールを使い暗号化・復号化を実装してみましょう。
EncryptControllerに新たに「encryptSpring」メソッドを作成しましょう

・EncryptController.java

//    SpringSecurity Crypto モジュールでの複合化・暗号化
    @GetMapping("/springEncrypt")
    public String encryptSpring(Model model) {
        // 暗号化する文字列
        String sampleText = "こんにちは";
        model.addAttribute("sampleText", sampleText);
        // 暗号化対象のデータ
        // 文字列をバイト配列に変換。
        final byte[] source = sampleText.getBytes(StandardCharsets.UTF_8);
        
        // 共通鍵
        String encryptSecretKey = "GOV2dkHGQcE1ZcX8";
        
        //  ソルト:暗号化されたデータが危険にさらされた場合に、キーに対する辞書攻撃を防ぐために使用されます。
        //   暗号化された各メッセージが一意になるように、KeyGenerator を使用して生成(16 進数でエンコードされた文字列形式で、ランダムで、長さが 8 バイト以上になる)
        String salt = KeyGenerators.string().generateKey();
        
        // BytesEncryptor
        BytesEncryptor binaryEncryptor = Encryptors.stronger(encryptSecretKey, salt);
        // Encryptors.stronger ファクトリメソッドを使用して BytesEncryptor を作成
        
        // バイナリ(GCM)
        // 暗号化
        String gcmEncryptedBinaryData = Base64.getEncoder()
                .encodeToString(binaryEncryptor.encrypt(source));
        model.addAttribute("gcmEncryptedBinaryData",gcmEncryptedBinaryData);
        // 復号
        // 結果を Base64 で文字列化して画面表示用に。
        // Base64 はバイナリを文字列として表現・表示するための形式。
        byte[] gcmDecryptedBinaryData = binaryEncryptor.decrypt(Base64.getDecoder().decode(gcmEncryptedBinaryData));
        // byte[]を文字列へ変換
        String gcmDecryptedText = new String(gcmDecryptedBinaryData,StandardCharsets.UTF_8);
        model.addAttribute("gcmDecryptedText",gcmDecryptedText);
        
        // バイナリ(CBC)
        // 暗号化
        String cbcEncryptedBinaryData = Base64.getEncoder()
                .encodeToString(binaryEncryptor.encrypt(sampleText.getBytes()));
        model.addAttribute("cbcEncryptedBinaryData",cbcEncryptedBinaryData);
        // 復号
        byte[] decryptedBinaryData = binaryEncryptor.decrypt(Base64.getDecoder().decode(cbcEncryptedBinaryData));
        // byte[]を文字列へ変換
        String cbcDecryptedText = new String(decryptedBinaryData, StandardCharsets.UTF_8);
        model.addAttribute("cbcDecryptedText",cbcDecryptedText);
        
        
        // GCM方式
        // 暗号化する文字列
        String gcmText = "GCM方式です";
        model.addAttribute("gcmText", gcmText);
        
        TextEncryptor aesGcmTextEncryptor = Encryptors.delux(encryptSecretKey , salt);
        //   暗号化
        String encryptedData = aesGcmTextEncryptor.encrypt(gcmText);
        model.addAttribute("encryptedData",encryptedData);
        // 復号
        String gcmDecrypted = aesGcmTextEncryptor.decrypt(encryptedData);
        model.addAttribute("gcmDecrypted",gcmDecrypted);
        
        // CBC方式
        // 暗号化する文字列
        String cbcText = "CBC方式です";
        model.addAttribute("cbcText", cbcText);
        
        TextEncryptor cbcTextEncryptor = Encryptors.text(cbcText, salt);
        // 暗号化
        String encryptedCbcData = cbcTextEncryptor.encrypt(cbcText);
        model.addAttribute("encryptedCbcData",encryptedCbcData);
        // 復号
        String decryptedCbcData = cbcTextEncryptor.decrypt(encryptedCbcData);
        model.addAttribute("decryptedCbcData",decryptedCbcData);

        return "encrypt/encryptSpring";
    }

出力用のViewとして「src/main/resources/templates/enctypt」フォルダ内に「encryptSpring.html」を作成します。
・encryptSpring.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="ja">

<head>
	<meta charset="UTF-8">
	<title>SpringSecurity: 共通鍵暗号化・復号</title>
</head>

<body>
	<h1>SpringSecurity: 共通鍵暗号化・復号</h1>
	<h2>GCM(バイナリ)</h2>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${sampleText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${gcmEncryptedBinaryData}"></span></p>
		</li>
		<li>
			<p>復号後テキスト:<span th:text="${gcmDecryptedText}"></span></p>
		</li>
	</ul>
	<h2>CBC(バイナリ)</h2>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${sampleText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${cbcEncryptedBinaryData}"></span></p>
		</li>
		<li>
			<p>復号後テキスト:<span th:text="${cbcDecryptedText}"></span></p>
		</li>
	</ul>
	<h2>GCM方式</h2>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${gcmText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${encryptedData}"></span></p>
		</li>
		<li>
			<p>復号後テキスト:<span th:text="${gcmDecrypted}"></span></p>
		</li>
	</ul>
	<h2>CBC方式</h2>
	<ul>
		<li>
			<p>暗号化前テキスト:<span th:text="${cbcText}"></span></p>
		</li>
		<li>
			<p>暗号化後テキスト:<span th:text="${encryptedCbcData}"></span></p>
		</li>
		<li>
			<p>復号後テキスト:<span th:text="${decryptedCbcData}"></span></p>
		</li>
	</ul>
</body>

</html>

ここでは4つの方法で暗号化・復号化を行っています。
なお、GCMもCBCもどちらも同じ binaryEncryptor(GCM)を使っているので違いはほぼありません。

BytesEncryptor

ここではGCM(Galois/Counter Mode)を用いたAESを使用してバイト配列の暗号化をしています。

// BytesEncryptor
 BytesEncryptor binaryEncryptor = Encryptors.stronger(encryptSecretKey, salt);

共通鍵とソルトを指定してEncryptors#strongerメソッドを呼び出し、
BytesEncryptorクラスのインスタンスを生成します。

stronger暗号化方式は、GCMで 256 ビット AES 暗号化を使用して暗号化装置を作成します。
standard暗号化法式というものもあり、こちらはCBC(暗号ブロック連鎖)モードの256ビットAESです。

このときに指定した共通鍵とソルトは、復号時にも同じものを利用します。

TextEncryptor

TextEncryptorはテキスト文字列の対称暗号化のためのサービスインターフェースです。
暗号化・復号化を以下のように記述して行います。

TextEncryptor 変数名 = Encryptors.text(暗号化する文字列, ソルト);

// 暗号化
String 変数名 = TextEncryptorインスタンスを格納した変数.encryot(暗号化する文字列);

// 復号化
String 変数名 = TextEncryptorインスタンスを格納した変数.decrypt(暗号化された文字列);

タイトルとURLをコピーしました