[アップデート] Amazon S3 がポスト量子暗号をサポートするようになりました
はじめに
皆様こんにちは、あかいけです。
2025年11月19日のアップデートにて、Amazon S3がポスト量子暗号のサポートを開始しました。
これにより、S3エンドポイントでML-KEM(Module Lattice-Based Key Encapsulation Mechanisms)を使用したポスト量子暗号が利用可能になりました。
というわけで今回は、このアップデートの内容と実際に検証した結果をまとめます。
ポスト量子暗号ってなんだろう。
従来のTLS暗号化で使われているRSAやECC(楕円曲線暗号)は、現在のコンピュータでは解読が事実上不可能です。
しかし、大規模な量子コンピュータが実用化されると、これらの暗号を破ることが可能になると考えられています。
特に懸念されるのが「Harvest Now, Decrypt Later」攻撃です。
攻撃者が今のうちに暗号化された通信を記録しておき、将来量子コンピュータが使えるようになったら復号化するというシナリオです。
そこでAWSは、従来のECDH(楕円曲線ディフィー・ヘルマン鍵交換)とNISTが標準化したML-KEM(格子ベース鍵カプセル化メカニズム)を組み合わせたハイブリッド方式を採用しています。
これにより、従来のTLSと同等以上のセキュリティを保ちながら、量子コンピュータへの耐性も確保しています。
対象サービスと利用可能リージョン
今回のアップデートで、以下の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.
検証してみる
S3バケット側
検証にあたり、S3バケットを作成します。
なおS3エンドポイントがポスト量子暗号を自動的にサポートするため、S3バケット側で特別な設定は不要です。
terraformコード
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など)の対応状況は以下ドキュメントに記載があるのでご参照ください。
今回はAWS SDK for Goを利用して、
ML-KEM対応のTLS接続を行うためのクライアント側の設定と検証方法を紹介します。
コード実装
AWS SDK for Goは、Go 1.24以降でPQ TLSがデフォルトで有効なため、特別な設定は不要です。
また実行内容としてはS3バケットに対してオプジェクトをput,get,deleteしているだけです。
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)
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の実行の前後で取得しています。
#!/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.
1. S3アクセスログ
通常のTLS情報は記録されますが、PQ TLSの鍵交換詳細は記録されていません。
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が使用されている」ことは直接確認できません。
{
"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の利用における、セキュリティ対策の参考になれば幸いです。







