暗号

AES-GCM 仕組みの解説とJavaサンプルコード

この記事で分かること

  • AES-GCMモードの特徴、仕組みについて
  • 既存の暗号モードとの違いについて
  • Javaのサンプルコードとその解説

この記事では、共通鍵暗号のデファクトスタンダードとなっている、AES-GCMモードについての解説をしています。

AES-GCMとは、近年様々な用途で用いられているAESの暗号モードの一つです。
主な利用用途には、IPsec、SSH、TLS 1.2/1.3 などがあります。

今回、この記事を書くに当たって色々なサイトを回り調査を行いました。
私自身、元々暗号についての卒論を書くほど暗号化について興味はありましたが、GCMモードについては知らないことが多く、新たな知識を得ることができました。

私が理解して噛み砕いた内容をなるべく簡単に理解できるよう解説しました。
Java のサンプルコードも載せていますので、ぜひ参考にしていただければと思います。

AES GCMモードの解説

この章では、AES GCMモードについて仕様を簡単に説明します。

AES (Advanced Encryption Standard)は、現在ブロック暗号のデファクトスタンダードとなっている共通鍵暗号アルゴリズム。
GCM (Galois/Counter Mode)は、ブロック暗号の暗号利用モードの一つであり、認証付き暗号の一つである。

従来の暗号モードと比較した際の、GCMモードの特徴は以下の通りとなっている。

GCMモードの特徴

  • 暗号化と認証(完全性確認)の両方の機能を同時に持つ
  • 平文のパディングが不要
  • 並列処理が可能で効率が良い
  • 暗号化時に TAGと呼ばれる認証用のデータが生成される
  • AADで追加の認証が可能

一つずつ解説していきます。

暗号化と認証の両方の機能を持つ

GCMモードの最大の特徴は、暗号化 / 復号の処理と同時に、そのデータが正しい人によって生成され、改ざんされていないことを認証できることです。
GCMとは、簡単に言えば「ガロア認証+カウンターモード」で暗号化するモードだと言えます。

カウンターモード (CTR mode)とは従来からある暗号モードの一つで、処理の並列化が可能で、パディングが不要な、効率の良い暗号モードのことです。
ガロア認証 (Galois Message Authentication Code (GMAC))とは、暗号文が改ざんされていなことを検知する仕組みのことです。

この両方の機能を同時に持つことが、現在最も安全な暗号モードとしてGCMが採用されている理由だと思います。

パディングが不要

カウンターモードを採用しているため、平文 (Plain Text)のパディングが不要です。
カウンターモードの仕組みとして、ブロック長と同じ長さのIVを暗号化した後、その結果を平文とXORするという処理方式のため、平文自体にはパディングが不要ということだと思います。

並列処理が可能

これもカウンターモードの特性に拠るものですが、任意の暗号ブロックから暗号化 / 復号を開始できます。
IVがカウンタ値になっているので、Nブロック目のカウンタ値が簡単に予想することができ、その結果並列で処理をすることが可能になります。

タグと呼ばれる認証データが生成される

GCMモードを理解する上では、タグ(Tag)という概念が重要になってきます。

GCMモードで暗号化すると、暗号文とは別にTagと呼ばれるデータも一緒に出力されます。
復号する際には、暗号文と共にこのTagも一緒に渡す必要があります。
Tagが想定される値と異なる場合、認証エラーが発生します。
Tagは、128, 120, 112, 104, 96 bitのいずれかの長さになるように設定できます。

AADと呼ばれる追加認証データの指定が可能

追加認証データ (AAD) と呼ばれるオプションの入力データを使って、AES-GCMとは違うレイヤーでの追加認証をすることができます。
暗号化の強度には直接は関係ありません。

利用用途の説明はこちらのドキュメントが分かりやすいです。
追加認証データ  |  Cloud KMS ドキュメント  |  Google Cloud

超分かりやすい解説動画

AES-GCM-GMAC
AES-GCM 処理フロー図

こちらは動画から抜粋した画像です。AES-GCM / GMACの処理フローを一枚で表しています。
英語の動画ですが、自動翻訳でも十分に理解できます。

この図は、AADありでAES-GCM暗号化を表した図です。

  • Ekは、Key=kで暗号化した結果のこと
  • PT1~PTnは、プレーンテキスト(平文)を16byteごとのブロックに分割したもの
  • CT1~CTnは、EkとPTnをXORしてできた暗号文
  • 0128は、0が128bit繋がったデータ
  • AD1~ADnは、AADを16byteごとに分割したもの
  • IV || 0~IV || nは、96bitのnonceと32bitのcounterを繋げたもの
  • GHASHとは128bitのデータを生成するハッシュ関数

暗号化/復号 サンプルコード

AES-GCMで暗号化/復号する Javaのサンプルコードを紹介します。

  • AESの鍵は、128, 192, 256 bitの中から選ぶことができます
  • nonceは必ず毎回ランダムな値になるように実装して下さい(重要)
  • nonceの長さは12 byteです (nonce + counter (4 byte)がIVになります)
  • Tagの長さは128, 120, 112, 104, 96 bitから選ぶことができます(長い方がより安全)
  • このサンプルコードの例では、Tagが cipherDataに含まれた形で出力されます
  • AADの指定は任意です。
  • AADの長さは64 KiB 以下であれば良いようです
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

public class AesGcmExample
{
    public static final int AES_KEY_BIT_LENGTH = 256; // Must be one of {128, 192, 256}
    public static final int GCM_NONCE_LENGTH = 12; // Nonce length
    public static final int GCM_TAG_BIT_LENGTH = 128; // Must be one of {128, 120, 112, 104, 96}

    private static String plainText = "This is a plain text which need to be encrypted by Java AES 256 GCM Encryption Algorithm";

    public static void main(String[] args) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        keyGenerator.init(AES_KEY_BIT_LENGTH);

        // Generate Key
        SecretKey key = keyGenerator.generateKey();
        byte[] nonce = new byte[GCM_NONCE_LENGTH];
        // Generate nonce ([IMPORTANT] ABSOLUTELY USE RANDOM VALUE EACH TIME!!!)
        SecureRandom random = new SecureRandom();
        random.nextBytes(nonce);
        // Generate AAD (AAD length must be less than 64KiB)
        byte[] aad = "This is an additional authenticated data. It's called the AAD. You can use any value."
                .getBytes(StandardCharsets.UTF_8);

        System.out.println("Original Text : " + plainText);

        byte[] encryptedData = encrypt(plainText.getBytes(), key, nonce, aad);
        System.out.println("Encrypted Text : " + Base64.getEncoder().encodeToString(encryptedData));

        byte[] decryptedData = decrypt(encryptedData, key, nonce, aad);
        System.out.println("Decrypted Text : " + new String(decryptedData, StandardCharsets.UTF_8));
    }

    public static byte[] encrypt(byte[] plainData, SecretKey key, byte[] nonce, byte[] aad) throws Exception
    {
        // Get Cipher Instance
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        // Create SecretKeySpec
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        // Create GCMParameterSpec
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_BIT_LENGTH, nonce);

        // Initialize Cipher for ENCRYPT_MODE
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);

        if (aad != null) {
            // Update AAD to do additional auth (Optional)
            cipher.updateAAD(aad);
        }

        // Perform Encryption
        byte[] cipherData = cipher.doFinal(plainData);

        return cipherData;
    }

    public static byte[] decrypt(byte[] cipherData, SecretKey key, byte[] nonce, byte[] aad) throws Exception
    {
        // Get Cipher Instance
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

        // Create SecretKeySpec
        SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");

        // Create GCMParameterSpec
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_BIT_LENGTH, nonce);

        // Initialize Cipher for DECRYPT_MODE
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);

        if (aad != null) {
            // Update AAD to verify additional auth (Optional)
            cipher.updateAAD(aad);
        }

        // Perform Decryption
        byte[] decryptedData = cipher.doFinal(cipherData);

        return decryptedData;
    }
}

まとめ

最後に、2022年現在最も安全で、利用価値が高いとされているAES-GCMモードの特徴をまとめます。

要点まとめ

  • AES-GCMは、暗号化と認証を同時に行うことができる共通鍵暗号方式である
  • 処理効率が良く、並列処理が可能で、パディングは不要である
  • 暗号化と同時にタグが出力される
  • 復号時には、暗号文とタグを渡して復号する。タグが一致しない場合、認証エラーとなる
  • AADと呼ばれる追加認証データを渡して暗号化/復号することができる(オプション)
  • 暗号化/復号時に渡したAADが一致しない場合エラーとなる

以上が調査結果です。少しでも参考になりましたら幸いです。

参考サイト

この記事は役に立ちましたか?

  • この記事を書いた人
アバター画像

ゴイチ

ソフトウェアエンジニア歴20年。 C/C++, C#, Java, Kotlinが得意で、組込系・スマホ・大規模なWebサービスなど幅広いプログラミング経験があります。 現在は某SNSの会社でWebエンジニアをしています。

-暗号
-, ,