java-recipes

ホーム セキュリティ › Sec-02

Sec-02: 基本的な暗号化(AES-256-GCM)

javax.crypto を使って AES-256-GCM による暗号化・復号を実装します。 IV(Initialization Vector:初期化ベクトル)のランダム生成と、GCM モードによる改ざん検出も解説します。

なぜデータを暗号化するのか

データベースや外部ストレージに機密情報(個人情報・クレジットカード番号・APIキーなど)を 保存する場合、平文(そのままの形)で保存するのは危険です。 データベースが漏えいした場合に情報がそのまま読み取られてしまいます。 暗号化することで、鍵を持たない攻撃者からデータを保護できます。

AES-256-GCM の特徴

  • AES-256: AES(Advanced Encryption Standard:高度暗号化標準)は現在最も広く使われている共通鍵暗号方式です。 256 ビットの鍵長は現在の技術では解読が事実上不可能です。
  • GCM モード(Galois/Counter Mode:ガロア/カウンタモード): 暗号化と同時にデータの完全性(改ざん検出)も保証します。 復号時にデータが改ざんされていると例外が発生するため、通信途中の改ざんを検出できます。 これを「認証付き暗号化(AEAD)」と呼びます。
  • IV(Initialization Vector:初期化ベクトル): 同じ平文を同じ鍵で暗号化しても毎回異なる暗号文になるよう、 ランダムな初期値を付加します。GCM では 12 バイトの IV が推奨されています。 IV は秘密にする必要はありませんが、同じ鍵で IV を再利用してはいけません。

サンプルコード

AesEncryptionSample.java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

public class AesEncryptionSample {

    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_IV_LENGTH = 12;  // GCM 推奨: 12 バイト
    private static final int GCM_TAG_LENGTH = 128; // 認証タグ: 128 ビット

    // AES-256 鍵を生成
    public static SecretKey generateKey() throws Exception {
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256, new SecureRandom()); // 256ビット鍵
        return keyGen.generateKey();
    }

    // 暗号化(IV をランダム生成して暗号文の先頭に付与)
    public static byte[] encrypt(String plainText, SecretKey key) throws Exception {
        byte[] iv = new byte[GCM_IV_LENGTH];
        new SecureRandom().nextBytes(iv); // ❗ 毎回必ず異なる IV を使う

        Cipher cipher = Cipher.getInstance(ALGORITHM);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);

        byte[] cipherText = cipher.doFinal(plainText.getBytes("UTF-8"));

        // IV + 暗号文を結合して返す(IV は復号時に必要)
        byte[] result = new byte[iv.length + cipherText.length];
        System.arraycopy(iv, 0, result, 0, iv.length);
        System.arraycopy(cipherText, 0, result, iv.length, cipherText.length);
        return result;
    }

    // 復号(先頭12バイトを IV として取り出す)
    public static String decrypt(byte[] encryptedData, SecretKey key) throws Exception {
        byte[] iv = Arrays.copyOfRange(encryptedData, 0, GCM_IV_LENGTH);
        byte[] cipherText = Arrays.copyOfRange(encryptedData, GCM_IV_LENGTH, encryptedData.length);

        Cipher cipher = Cipher.getInstance(ALGORITHM);
        GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);

        return new String(cipher.doFinal(cipherText), "UTF-8");
    }

    public static void main(String[] args) throws Exception {
        System.out.println("=== AES-256-GCM 暗号化デモ ===");

        SecretKey key = generateKey();
        String plainText = "機密データ: パスワードは password123 です";

        byte[] encrypted = encrypt(plainText, key);
        System.out.println("暗号文(Base64): " + Base64.getEncoder().encodeToString(encrypted));

        String decrypted = decrypt(encrypted, key);
        System.out.println("復号結果: " + decrypted);

        System.out.println("\n=== 改ざん検出(GCM の認証機能) ===");
        byte[] tampered = Arrays.copyOf(encrypted, encrypted.length);
        tampered[20] ^= 0xFF; // データを1バイト改ざん
        try {
            decrypt(tampered, key);
        } catch (Exception e) {
            System.out.println("改ざん検出: " + e.getClass().getSimpleName());
        }
    }
}

Java 8 から AES/GCM/NoPadding が標準ライブラリで利用できます。IV(Initialization Vector:初期化ベクトル)は毎回 SecureRandom で生成し、暗号文の先頭に付与して保存します。

よくあるミス・注意点

⚠️ IV(初期化ベクトル)を固定値にする

同じ鍵と IV の組み合わせで複数の平文を暗号化すると、暗号文のパターンから元のデータが解析されるリスクがあります。 GCM モードでは特に危険で、鍵の安全性が完全に崩れる場合があります。 IV は必ず SecureRandom で 暗号化のたびにランダム生成し、暗号文と一緒に保存してください。

⚠️ ECB モードを使う

ECB(Electronic Codebook)モードは同じ平文ブロックが常に同じ暗号文ブロックになります。 そのため、データ中のパターンが暗号文にも現れてしまいます(例: 同じ内容のブロックが同じ暗号文になる)。AES/ECB/PKCS5Padding は使わず、 必ず AES/GCM/NoPadding を使いましょう。

⚠️ 鍵をソースコードにハードコードする

暗号化の鍵をソースコード中に直接書くと、Git リポジトリに機密情報が残ります。 一度コミットされたシークレットは履歴から消すのが困難です。 鍵は環境変数・Vault・KMS(Key Management Service)などの安全な場所で管理し、 実行時に読み込む設計にしてください。

⚠️ IV を暗号文とは別に管理する

IV は復号時に必要です。暗号文とは別のカラムに保存するなど管理が複雑になりがちです。 本サンプルのように暗号文の先頭 12 バイトに IV を付与して一緒に保存すると、 管理が簡単になります。IV は秘密情報ではないので、暗号文と一緒に保存して問題ありません。

テストする観点

  • 暗号化 → 復号を行うと元のテキストと一致すること(往復変換)
  • 日本語・マルチバイト文字を含むテキストが正しく暗号化・復号できること
  • 同じ平文を同じ鍵で2回暗号化した場合に、異なる暗号文が生成されること(IV のランダム性)
  • 改ざんした暗号文(バイト列を1バイト変更)を復号すると例外が発生すること(GCM の改ざん検出)
  • 異なる鍵で復号しようとすると例外が発生すること
  • 空文字列が正常に暗号化・復号できること(境界値)
  • 非常に長いテキストが正常に暗号化・復号できること(境界値)

GitHub でソースコードを見る →