[アップデート] Amazon S3 がポスト量子暗号をサポートするようになりました

[アップデート] Amazon S3 がポスト量子暗号をサポートするようになりました

2025.11.24

はじめに

皆様こんにちは、あかいけです。

2025年11月19日のアップデートにて、Amazon S3がポスト量子暗号のサポートを開始しました。
これにより、S3エンドポイントでML-KEM(Module Lattice-Based Key Encapsulation Mechanisms)を使用したポスト量子暗号が利用可能になりました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/11/s3-post-quantum-tls-key-exchange-endpoints/

というわけで今回は、このアップデートの内容と実際に検証した結果をまとめます。

ポスト量子暗号ってなんだろう。

従来のTLS暗号化で使われているRSAやECC(楕円曲線暗号)は、現在のコンピュータでは解読が事実上不可能です。
しかし、大規模な量子コンピュータが実用化されると、これらの暗号を破ることが可能になると考えられています。

特に懸念されるのが「Harvest Now, Decrypt Later」攻撃です。
攻撃者が今のうちに暗号化された通信を記録しておき、将来量子コンピュータが使えるようになったら復号化するというシナリオです。

そこでAWSは、従来のECDH(楕円曲線ディフィー・ヘルマン鍵交換)とNISTが標準化したML-KEM(格子ベース鍵カプセル化メカニズム)を組み合わせたハイブリッド方式を採用しています。
これにより、従来のTLSと同等以上のセキュリティを保ちながら、量子コンピュータへの耐性も確保しています。

https://aws.amazon.com/jp/security/post-quantum-cryptography/

https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryptionInTransit.PQ-TLS.html

対象サービスと利用可能リージョン

今回のアップデートで、以下のS3の機能でポスト量子暗号がサポートされました。
そして全AWSリージョンで追加費用なしで利用可能です。

  • Amazon S3(標準バケット)
  • S3 Express One Zone
  • S3 Tables

逆に以下のサービスでは現状では利用できません。

  • AWS PrivateLink for Amazon S3
  • Multi-Region Access Points
  • S3 Vectors

Hybrid post-quantum TLS is supported for all S3 endpoints except for AWS PrivateLink for Amazon S3, Multi-Region Access Points, and S3 Vectors.

https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryptionInTransit.PQ-TLS.html

検証してみる

S3バケット側

検証にあたり、S3バケットを作成します。
なおS3エンドポイントがポスト量子暗号を自動的にサポートするため、S3バケット側で特別な設定は不要です。

terraformコード
main.tf
terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

# 現在のAWSアカウント情報を取得
data "aws_caller_identity" "current" {}

# S3バケットの作成
resource "aws_s3_bucket" "pq_tls_bucket" {
  bucket        = "my-post-quantum-secure-bucket"
  force_destroy = true

  tags = {
    Name        = "Post-Quantum TLS Secure Bucket"
    Environment = "Production"
    Security    = "Post-Quantum-Ready"
  }
}

# TLS 1.3を強制するバケットポリシー
resource "aws_s3_bucket_policy" "pq_tls_bucket_policy" {
  bucket = aws_s3_bucket.pq_tls_bucket.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "DenyInsecureConnections"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource = [
          aws_s3_bucket.pq_tls_bucket.arn,
          "${aws_s3_bucket.pq_tls_bucket.arn}/*"
        ]
        Condition = {
          NumericLessThan = {
            "s3:TlsVersion" = "1.3"
          }
        }
      },
      {
        Sid       = "EnforceHTTPS"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:*"
        Resource = [
          aws_s3_bucket.pq_tls_bucket.arn,
          "${aws_s3_bucket.pq_tls_bucket.arn}/*"
        ]
        Condition = {
          Bool = {
            "aws:SecureTransport" = "false"
          }
        }
      }
    ]
  })
}

# ログ保存用のバケット
resource "aws_s3_bucket" "pq_tls_log_bucket" {
  bucket        = "my-post-quantum-secure-bucket-logs"
  force_destroy = true

  tags = {
    Name        = "Post-Quantum TLS Bucket Logs"
    Environment = "Production"
  }
}

# ログバケットのバケットポリシー
resource "aws_s3_bucket_policy" "pq_tls_log_bucket_policy" {
  bucket = aws_s3_bucket.pq_tls_log_bucket.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AllowS3ServerAccessLogging"
        Effect = "Allow"
        Principal = {
          Service = "logging.s3.amazonaws.com"
        }
        Action   = "s3:PutObject"
        Resource = "${aws_s3_bucket.pq_tls_log_bucket.arn}/log/*"
        Condition = {
          StringEquals = {
            "aws:SourceAccount" = data.aws_caller_identity.current.account_id
          }
        }
      }
    ]
  })
}

# メインバケットのログ設定
resource "aws_s3_bucket_logging" "pq_tls_bucket_logging" {
  bucket = aws_s3_bucket.pq_tls_bucket.id

  target_bucket = aws_s3_bucket.pq_tls_log_bucket.id
  target_prefix = "log/"

  depends_on = [aws_s3_bucket_policy.pq_tls_log_bucket_policy]
}

# CloudTrail用のS3バケット
resource "aws_s3_bucket" "cloudtrail_bucket" {
  bucket        = "my-post-quantum-cloudtrail-logs"
  force_destroy = true

  tags = {
    Name        = "Post-Quantum CloudTrail Logs"
    Environment = "Production"
  }
}

# CloudTrailバケットポリシー
resource "aws_s3_bucket_policy" "cloudtrail_bucket_policy" {
  bucket = aws_s3_bucket.cloudtrail_bucket.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AWSCloudTrailAclCheck"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:GetBucketAcl"
        Resource = aws_s3_bucket.cloudtrail_bucket.arn
        Condition = {
          StringEquals = {
            "AWS:SourceArn" = "arn:aws:cloudtrail:ap-northeast-1:${data.aws_caller_identity.current.account_id}:trail/pq-tls-s3-data-events"
          }
        }
      },
      {
        Sid    = "AWSCloudTrailWrite"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:PutObject"
        Resource = "${aws_s3_bucket.cloudtrail_bucket.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
        Condition = {
          StringEquals = {
            "AWS:SourceArn" = "arn:aws:cloudtrail:ap-northeast-1:${data.aws_caller_identity.current.account_id}:trail/pq-tls-s3-data-events"
            "s3:x-amz-acl"  = "bucket-owner-full-control"
          }
        }
      }
    ]
  })
}

# CloudTrail証跡の作成
resource "aws_cloudtrail" "pq_tls_trail" {
  name                          = "pq-tls-s3-data-events"
  s3_bucket_name                = aws_s3_bucket.cloudtrail_bucket.id
  include_global_service_events = true
  is_multi_region_trail         = false
  enable_logging                = true

  event_selector {
    read_write_type           = "All"
    include_management_events = false

    data_resource {
      type = "AWS::S3::Object"
      values = [
        "${aws_s3_bucket.pq_tls_bucket.arn}/*"
      ]
    }
  }

  tags = {
    Name        = "Post-Quantum TLS S3 Data Events"
    Environment = "Production"
  }

  depends_on = [aws_s3_bucket_policy.cloudtrail_bucket_policy]
}

# 出力設定
output "bucket_name" {
  description = "The name of the S3 bucket"
  value       = aws_s3_bucket.pq_tls_bucket.id
}

output "bucket_arn" {
  description = "The ARN of the S3 bucket"
  value       = aws_s3_bucket.pq_tls_bucket.arn
}

output "log_bucket_name" {
  description = "The name of the log bucket"
  value       = aws_s3_bucket.pq_tls_log_bucket.id
}

output "cloudtrail_bucket_name" {
  description = "The name of the CloudTrail bucket"
  value       = aws_s3_bucket.cloudtrail_bucket.id
}

output "cloudtrail_name" {
  description = "The name of the CloudTrail trail"
  value       = aws_cloudtrail.pq_tls_trail.name
}

クライアント側

各クライアント(SDKやAWS CLIなど)の対応状況は以下ドキュメントに記載があるのでご参照ください。

https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryptionInTransit.PQ-TLS.html
https://docs.aws.amazon.com/payment-cryptography/latest/userguide/pqtls-details.html

今回はAWS SDK for Goを利用して、
ML-KEM対応のTLS接続を行うためのクライアント側の設定と検証方法を紹介します。

コード実装

AWS SDK for Goは、Go 1.24以降でPQ TLSがデフォルトで有効なため、特別な設定は不要です。
また実行内容としてはS3バケットに対してオプジェクトをput,get,deleteしているだけです。

main.go
package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"

	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
	ctx := context.TODO()

	bucketName := "my-post-quantum-secure-bucket"
	region := "ap-northeast-1"

	// AWS設定のロード
	cfg, err := config.LoadDefaultConfig(ctx,
		config.WithRegion(region),
	)
	if err != nil {
		log.Fatalf("設定のロードに失敗: %v", err)
	}

	client := s3.NewFromConfig(cfg)

	objectKey := "pq-tls-test.txt"
	content := "Post-Quantum TLS Test from Go SDK"

	// オブジェクトのアップロード
	fmt.Println("=== オブジェクトアップロードテスト ===")
	_, err = client.PutObject(ctx, &s3.PutObjectInput{
		Bucket: &bucketName,
		Key:    &objectKey,
		Body:   bytes.NewReader([]byte(content)),
	})
	if err != nil {
		log.Fatalf("アップロードに失敗: %v", err)
	}
	fmt.Printf("✓ アップロード成功: %s\n\n", objectKey)

	// オブジェクトのダウンロード
	fmt.Println("=== オブジェクトダウンロードテスト ===")
	result, err := client.GetObject(ctx, &s3.GetObjectInput{
		Bucket: &bucketName,
		Key:    &objectKey,
	})
	if err != nil {
		log.Fatalf("ダウンロードに失敗: %v", err)
	}
	defer result.Body.Close()

	body, err := io.ReadAll(result.Body)
	if err != nil {
		log.Fatalf("データ読み込みに失敗: %v", err)
	}

	if string(body) == content {
		fmt.Printf("✓ ダウンロード成功\n")
		fmt.Printf("  内容: %s\n\n", string(body))
	}

	// クリーンアップ
	fmt.Println("=== クリーンアップ ===")
	_, err = client.DeleteObject(ctx, &s3.DeleteObjectInput{
		Bucket: &bucketName,
		Key:    &objectKey,
	})
	if err != nil {
		log.Fatalf("削除に失敗: %v", err)
	}
	fmt.Printf("✓ テストオブジェクト削除完了\n")
}

依存関係(go.mod)

go.mod
module s3-pq-tls-example

go 1.25

require (
	github.com/aws/aws-sdk-go-v2/config v1.28.7
	github.com/aws/aws-sdk-go-v2/service/s3 v1.71.1
)

スクリプト

パケットキャプチャを取得するため、
tcpdumpをmain.goの実行の前後で取得しています。

run_with_capture.sh
#!/bin/bash

CAPTURE_FILE="/tmp/s3_pq_tls_capture.pcap"

# sudo権限を確保
sudo -v

# 既存のキャプチャファイルを削除
sudo rm -f "$CAPTURE_FILE"

# tcpdumpをバックグラウンドで起動
sudo tcpdump -i any -w "$CAPTURE_FILE" -s 0 "tcp port 443" &
TCPDUMP_PID=$!

# tcpdumpが起動するまで待機(キャプチャファイルが作成されるまで)
for i in {1..10}; do
    [ -f "$CAPTURE_FILE" ] && break
    sleep 0.5
done

# Goプログラムを実行
go run .

# tcpdumpを停止(sudo+tcpdump両方のプロセスを終了)
sudo pkill -INT -f "tcpdump.*$CAPTURE_FILE" 2>/dev/null
sudo pkill -9 -f "tcpdump.*$CAPTURE_FILE" 2>/dev/null

# キャプチャファイルの権限を変更
sudo chmod 644 "$CAPTURE_FILE" 2>/dev/null
echo "Capture file: $CAPTURE_FILE"

スクリプトの実行

まずはGoのバージョンの確認、および依存関係をインストールします。

# Go 1.24以降であることを確認
go version

# 依存関係のインストール
go mod tidy

次にスクリプトを実行してパケットキャプチャとS3操作を同時に実行します。

./run_with_capture.sh
実行結果
=== オブジェクトアップロードテスト ===
✓ アップロード成功: pq-tls-test.txt

=== オブジェクトダウンロードテスト ===
✓ ダウンロード成功
  内容: Post-Quantum TLS Test from Go SDK

=== クリーンアップ ===
✓ テストオブジェクト削除完了
82 packets captured
102 packets received by filter
0 packets dropped by kernel
Capture file: /tmp/s3_pq_tls_capture.pcap

パケットキャプチャの分析

取得したパケットキャプチャから、実際にPQ TLSが使われているか確認していきます。

1. ClientHelloの確認(クライアントが提示した鍵交換方式)

TLSハンドシェイクの最初にクライアントが送信する「ClientHello」メッセージには、クライアントがサポートする鍵交換方式のリストが含まれています。
このリストの中から、サーバーが1つを選択してネゴシエーションが行われます。

以下のコマンドで、ClientHelloメッセージから「Supported Groups」(鍵交換で使用する楕円曲線・グループ)を抽出します。

tshark -r /tmp/s3_pq_tls_capture.pcap \
  -Y 'tls.handshake.type == 1 and tls.handshake.extensions_server_name contains "s3.ap-northeast-1.amazonaws.com"' \
  -V | grep -E 'Supported Group:' | head -10
  • オプション
    • -r: キャプチャファイルを読み込み
    • -Y: 表示フィルタ(tls.handshake.type == 1でClientHelloのみ抽出)
    • -V: 詳細表示モード
    • grep -E 'Supported Group:': 鍵交換で使用する楕円曲線・グループを抽出

結果として、リストの一番上にX25519MLKEM768がありました。
TLSのネゴシエーションでは、クライアントは優先度順にリストを提示するため、Go 1.25はPQ TLSを最優先で使おうとしていることが分かります。

出力結果
Supported Group: X25519MLKEM768 (0x11ec)
Supported Group: x25519 (0x001d)
Supported Group: secp256r1 (0x0017)
Supported Group: secp384r1 (0x0018)
Supported Group: secp521r1 (0x0019)
2. ServerHelloの確認(サーバーが選択した鍵交換方式)

次に、サーバーがどの方式を選択したかを確認します。
まず、通信先のS3エンドポイントのIPアドレスを特定します。

tshark -r /tmp/s3_pq_tls_capture.pcap \
  -Y 'tls.handshake.extensions_server_name contains "s3.ap-northeast-1.amazonaws.com"' \
  -T fields -e ip.dst
  • オプション
    • -T fields: フィールド形式で出力
    • -e ip.dst: 宛先IPアドレスのみを抽出
出力結果
52.219.151.90

このIPアドレスを使って、サーバーから返される「ServerHello」メッセージを確認します。

tshark -r /tmp/s3_pq_tls_capture.pcap \
  -Y 'ip.src == 52.219.151.90 and tls.handshake.type == 2' \
  -V | grep -B 5 -A 10 "key_share"
  • オプション
    • ip.src == 52.219.151.90: 指定したIPアドレスから送信されたパケット
    • tls.handshake.type == 2: ServerHelloメッセージ(type=2)

出力結果は以下の通りであり、
クライアントとサーバーの両方がX25519MLKEM768でネゴシエーションを完了し、実際にPQ TLSが確立されていることがわかります。

  • Extension: key_share (len=1124) X25519MLKEM768: サーバーがX25519MLKEM768を選択
  • Key Exchange Length: 1120: 鍵交換データが1120バイト
出力結果
Extensions Length: 1134
Extension: supported_versions (len=2) TLS 1.3
    Type: supported_versions (43)
    Length: 2
    Supported Version: TLS 1.3 (0x0304)
Extension: key_share (len=1124) X25519MLKEM768
    Type: key_share (51)
    Length: 1124
    Key Share extension
        Key Share Entry: Group: X25519MLKEM768, Key Exchange length: 1120
            Group: X25519MLKEM768 (4588)
            Key Exchange Length: 1120
            Key Exchange […]: 6ac7dabadb309a8fb10012197cf3e722a6c028ce6fa9e04f2d07bf87d6a723013bb215f264641810f83d2ffcbe75a9304e7d17aaff2c9479d71c6aa3c8384e75a2351724573d361319651f0b1ab49e95b7b9c5823cbfa872417263ab0e4c4e0e7573acc57637e538aafd55093a9
[JA3S Fullstring: 771,4865,43-51]
[JA3S: f4febc55ea12b31ae17cfb7e614afda8]

検証結果のまとめ

以上の検証から、Go 1.24以降とAWS S3の組み合わせで、特に設定せずにPQ TLSが自動的に使用されることが確認できました。

  • Go 1.25のクライアントは、追加設定なしでX25519MLKEM768を提示する
  • AWS S3サーバーはX25519MLKEM768を選択してTLSハンドシェイクを完了している

その他の確認方法

公式ドキュメントにてS3アクセスログやCloudTrailログにはPQ TLSの詳細情報は記録されないとのことでしたが、念の為確認してみます。

PQ-TLS key exchange details for requests to Amazon S3 are not available in AWS CloudTrail events or S3 server access logs.

https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingEncryptionInTransit.PQ-TLS.html#pqtls-details

1. S3アクセスログ

通常のTLS情報は記録されますが、PQ TLSの鍵交換詳細は記録されていません。

S3アクセスログ
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2 my-post-quantum-secure-bucket [23/Nov/2025:16:07:41 +0000] 203.0.113.10 arn:aws:sts::123456789012:assumed-role/example-role/example-user ABCDEFGH12345678 REST.PUT.OBJECT pq-tls-test.txt "PUT /pq-tls-test.txt?x-id=PutObject HTTP/1.1" 200 - - 33 47 20 "-" "aws-sdk-go-v2/1.32.7 ua/2.1 os/macos lang/go#1.25.0 md/GOOS#darwin md/GOARCH#arm64 api/s3#1.71.1 m/E" - ExampleRequestId1234567890abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ SigV4 TLS_AES_128_GCM_SHA256 AuthHeader my-post-quantum-secure-bucket.s3.ap-northeast-1.amazonaws.com TLSv1.3 - -

2. CloudTrail

tlsDetailsにてTLSにて通信を行っていることは記録されますが、PQ TLS固有の情報は含まれません
したがって、CloudTrailでは「TLS 1.3が使用されている」ことは確認できますが、「PQ TLSが使用されている」ことは直接確認できません。

CloudTrailデータイベント(一部抜粋)
{
    "Records": [
        {
            "eventTime": "2025-11-23T16:07:41Z",
            "eventSource": "s3.amazonaws.com",
            "eventName": "HeadBucket",
            "awsRegion": "ap-northeast-1",
            "sourceIPAddress": "203.0.113.10",
            "userAgent": "[APN/1.0 HashiCorp/1.0 Terraform/1.6.6 (+https://www.terraform.io) terraform-provider-aws/6.22.1 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go-v2/1.32.7 ua/2.1 os/macos lang/go#1.25.0 md/GOOS#darwin md/GOARCH#arm64 api/s3#1.71.1 m/n]",
            "requestParameters": {
                "bucketName": "my-post-quantum-secure-bucket",
                "Host": "my-post-quantum-secure-bucket.s3.ap-northeast-1.amazonaws.com"
            },
            "responseElements": null,
            "additionalEventData": {
                "SignatureVersion": "SigV4",
                "CipherSuite": "TLS_AES_128_GCM_SHA256",
                "bytesTransferredIn": 0,
                "AuthenticationMethod": "AuthHeader",
                "x-amz-id-2": "ExampleRequestId1234567890abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ=",
                "bytesTransferredOut": 0
            },
            "requestID": "ABCDEFGH12345678",
            "eventID": "a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6",
            "readOnly": true,
            "resources": [
                {
                    "accountId": "123456789012",
                    "type": "AWS::S3::Bucket",
                    "ARN": "arn:aws:s3:::my-post-quantum-secure-bucket"
                },
                {
                    "type": "AWS::S3::Object",
                    "ARNPrefix": "arn:aws:s3:::my-post-quantum-secure-bucket/"
                }
            ],
            "eventType": "AwsApiCall",
            "managementEvent": false,
            "recipientAccountId": "123456789012",
            "eventCategory": "Data",
            "tlsDetails": {
                "tlsVersion": "TLSv1.3",
                "cipherSuite": "TLS_AES_128_GCM_SHA256",
                "clientProvidedHostHeader": "my-post-quantum-secure-bucket.s3.ap-northeast-1.amazonaws.com"
            }
        }
    ]
}

さいごに

以上、Amazon S3でのポスト量子暗号のサポートについてまとめました。

実際に量子コンピュータの脅威が実害として現れるのは将来的なものと思われますが、「Harvest Now, Decrypt Later」攻撃のリスクを考えると、今から対策を始めることが重要です。
また追加コストなしで対策を導入できるのは大きなメリットなので、
長期的な機密性が求められるデータを扱う場合は、クライアント側の設定を見直してML-KEM対応を検討すると良さそうです。

この記事が皆様のS3の利用における、セキュリティ対策の参考になれば幸いです。

この記事をシェアする

FacebookHatena blogX

関連記事