暗号化とは特定の人にのみにしかわからないようにデータを暗号にすることで、
復号化は、暗号化されたデータを暗号化する前の状態へ戻すことです。
暗号化
「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(暗号化された文字列);
