💭

【アップデート】CloudFrontの署名付きURLでECDSA鍵を利用可能に!──実測で91%高速化、55%のURL短縮効果

に公開

Contents

はじめに

2025 年 9 月 9 日、Amazon CloudFront の署名付き URL に ECDSA(Elliptic Curve Digital Signature Algorithm: 楕円曲線デジタル署名アルゴリズム)がサポートされました。これまでの RSA-2048 に加えて、ECDSA P-256(prime256v1)が利用可能になったことで、より高速な署名生成と短い URL 生成が実現できます。

本記事では、実際に ECDSA キーを使用した署名付き URL 生成から性能比較までを紹介します。

https://aws.amazon.com/about-aws/whats-new/2025/09/amazon-cloudfront-ecdsa-signed-urls/

アップデート概要

主な変更点

  • CloudFront の署名付き URL で ECDSA(P-256)がサポートされた
  • これまで RSA ベースの暗号化アルゴリズムのみだったが、RSA と ECDSA の両方から選択可能になった
  • より高速な署名生成と短い URL 生成が可能になった

期待されるメリット

  • 署名生成速度の向上: ECDSA は RSA より高速な署名処理が可能なため
  • URL 長の短縮: 署名サイズが小さいため URL 全体が短くなるため
  • 計算効率の改善: より少ない CPU リソースで同等のセキュリティを実現可能なため

検証準備

1. キーペアを生成

ECDSA キーペアを生成

# ECDSA P-256 秘密鍵を生成
openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-private.pem

# 公開鍵を生成
openssl ec -in ecdsa-private.pem -pubout -out ecdsa-public.pem

RSA キーペアを生成

# RSA 2048ビット鍵
openssl genrsa -out rsa-private.pem 2048
openssl rsa -pubout -in rsa-private.pem -out rsa-public.pem

生成されたキーを確認

# ECDSA鍵の詳細確認
openssl ec -in ecdsa-private.pem -text -noout
#read EC key
#Private-Key: (256 bit)
#priv:
#    05:70:ee:e3:9a:43:a3:a9:43:0c:03:c6:c5:51:92:
#    98:9b:bb:aa:f9:e8:04:c9:fc:f3:70:e6:1c:ec:5e:
#    fb:7f
#pub:
#    04:3f:a0:64:f0:48:dc:37:fc:6a:9d:f9:f3:c2:ad:
#    ca:e9:a4:fb:56:ee:be:30:1c:fc:24:09:07:d8:92:
#    92:e7:7e:aa:21:ab:e4:17:da:3c:9d:f1:35:1e:04:
#    ca:a1:87:3f:9c:f6:c7:78:9c:03:84:23:4f:28:da:
#    b1:fc:d1:13:4d
#ASN1 OID: prime256v1
#NIST CURVE: P-256

# RSA鍵の詳細確認
openssl rsa -in rsa-private.pem -text -noout
#RSA Private-Key: (2048 bit, 2 primes)
#modulus:
#    00:b9:65:c9:7f:5d:83:39:b9:41:9f:20:c0:ba:82:
#    a6:33:c5:ca:aa:d0:10:33:87:04:9d:dd:34:46:1a:
#    27:71:aa:69:3e:60:ea:7a:51:c5:df:37:f9:3b:bb:
#    74:64:ef:9a:ce:82:14:df:cc:9c:9d:1a:2b:72:e9:
#    76:22:c9:86:b1:e9:1b:bd:69:24:ce:5e:54:60:0b:
#    0f:ed:24:ba:02:af:e1:84:17:d5:aa:18:bd:a5:a0:
#    4a:32:64:8e:df:89:97:31:49:40:05:fb:36:ba:aa:
#    a5:55:da:98:a6:d1:c8:8e:54:b5:a9:82:08:d9:38:
#    e6:77:c8:86:16:6c:15:0a:0e:49:ce:c7:c1:b5:6b:
#    ee:fd:f5:39:ba:49:99:07:08:3f:99:ec:24:e8:84:
#    50:b7:cc:00:d7:54:aa:69:47:54:5c:9c:e9:f0:01:
#    80:f9:a0:ee:81:a9:b9:96:26:d3:d2:0e:cf:fc:fe:
#    a3:ca:35:de:1e:15:6a:4d:4e:bb:55:40:01:cd:37:
#    0d:bf:0a:fb:ac:88:5d:ec:06:94:9a:71:66:1d:0c:
#    8d:5f:03:da:bb:b6:3f:4d:00:b9:f7:73:c3:a2:4e:
#    c6:1c:ad:59:3c:da:de:d7:2c:e7:14:75:f3:76:eb:
#    a3:ba:65:31:4a:82:d7:a0:44:0b:92:e8:20:fd:16:
#    77:6b
#publicExponent: 65537 (0x10001)
#privateExponent:
#    7b:08:d5:a3:b4:87:35:73:7f:dc:fe:7b:68:4a:f3:
#    c9:3f:e5:b3:5d:c8:a2:a9:63:a2:ef:fd:94:d8:1e:
#    7d:0c:3e:33:ab:55:70:3f:7c:35:1a:8c:47:db:aa:
#    ee:a1:23:f7:0f:d4:92:80:d3:1e:5f:13:9e:7a:d8:
#    6f:55:96:11:d3:45:19:7a:01:77:02:9a:b3:f2:23:
#    6b:99:07:e8:68:98:1c:75:44:d2:51:00:ea:3d:69:
#    7f:ef:c3:f1:0c:fa:4d:36:f3:4a:04:e0:d9:18:0e:
#    08:df:bc:96:58:79:e4:1a:cb:43:e6:8b:c0:bc:46:
#    16:0f:f9:0c:40:45:e6:19:5c:23:16:a4:65:86:2e:
#    dd:f2:cd:84:02:05:55:18:b0:4c:02:a0:f3:05:76:
#    0d:74:80:db:a0:9a:e8:cc:85:72:06:82:79:32:a2:
#    30:fc:34:19:78:cd:ea:27:10:76:4e:57:d3:67:9b:
#:(略)

2. CloudFront への公開鍵登録

ECDSA 公開鍵の登録

#!/bin/bash

ECDSA_KEY_NAME="my-ecdsa-signing-key"
ECDSA_CALLER_REFERENCE=$(date +%s)

# パブリックキーの内容を取得
ECDSA_KEY_CONTENT=$(cat ecdsa-public.pem)

# パブリックキーを作成
ECDSA_KEY_RESPONSE=$(aws cloudfront create-public-key \
    --public-key-config \
    Name="$ECDSA_KEY_NAME",CallerReference="$ECDSA_CALLER_REFERENCE",EncodedKey="$ECDSA_KEY_CONTENT",Comment="ECDSA P-256 key for signed URLs")
# パブリックキーIDを取得
export ECDSA_KEY_ID=$(echo $ECDSA_KEY_RESPONSE | jq -r '.PublicKey.Id')
echo "Created Public Key ID: $ECDSA_KEY_ID"

RSA 公開鍵の登録

RSA_KEY_NAME="my-rsa-signing-key"
RSA_CALLER_REFERENCE=$(date +%s)

# パブリックキーの内容を取得
RSA_KEY_CONTENT=$(cat rsa-public.pem)

# パブリックキーを作成
RSA_KEY_RESPONSE=$(aws cloudfront create-public-key \
    --public-key-config \
    Name="$RSA_KEY_NAME",CallerReference="$RSA_CALLER_REFERENCE",EncodedKey="$RSA_KEY_CONTENT",Comment="RSA 2048 key for signed URLs")
# パブリックキーIDを取得
export RSA_KEY_ID=$(echo $RSA_KEY_RESPONSE | jq -r '.PublicKey.Id')
echo "Created Public Key ID: $RSA_KEY_ID"

3. CloudFront へのキーグループ作成

ECDSA キーグループ作成

# ECDSAキーグループ作成
ECDSA_KEY_GROUP_RESPONSE=$(aws cloudfront create-key-group \
    --key-group-config \
    Name="ecdsa-key-group",Items="$ECDSA_KEY_ID",Comment="ECDSA key group for signed URLs")
# キーグループIDを取得
ECDSA_KEY_GROUP_ID=$(echo $ECDSA_KEY_GROUP_RESPONSE | jq -r '.KeyGroup.Id')
echo "Created Key Group ID: $ECDSA_KEY_GROUP_ID"

RSA キーグループ作成

# RSAキーグループ作成
RSA_KEY_GROUP_RESPONSE=$(aws cloudfront create-key-group \
    --key-group-config \
    Name="rsa-key-group",Items="$RSA_KEY_ID",Comment="RSA key group for signed URLs")
# キーグループIDを取得
RSA_KEY_GROUP_ID=$(echo $RSA_KEY_GROUP_RESPONSE | jq -r '.KeyGroup.Id')
echo "Created Key Group ID: $RSA_KEY_GROUP_ID"

4. CloudFront ディストリビューション設定

⚠️ これを忘れると署名付き URL を生成しても表示できません!!

AWS Management Console で以下を設定します。

  1. ディストリビューションの「Behaviors」タブ
  2. 対象のビヘイビアで「Edit」を選択
  3. 「Restrict viewer access」を「Yes」に設定
  4. 「Trusted authorization type」を「Trusted key groups」に設定
  5. 作成したキーグループを選択

5. 署名付き URL 生成プログラムの実装

AWS CLI の制限事項

⚠️ 重要: 2025 年 9 月 9 日時点で、AWS CLI のcloudfront signコマンドは ECDSA キーをサポートしていないためか、RSA ではないというエラーがでます。

#aws --version
#aws-cli/2.29.0 Python/3.13.7 Linux/5.15.167....
# AWS CLIでECDSA鍵を使用するとエラーが発生
aws cloudfront sign \
    --url https://example.com/file.jpg \
    --key-pair-id $ECDSA_KEY_ID \
    --private-key file://ecdsa-private.pem \
    --date-less-than "2025-09-10T12:09:03Z"
# エラー: RSA private key not found in PEM.

Python 実装

ここは生成 AI にお願いしました。

ECDSA 署名付き URL 生成
# ecdsa_signer.py
import sys
import time
from botocore.signers import CloudFrontSigner
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.backends import default_backend
import os
from datetime import datetime

def ecdsa_signer(message):
    with open("ecdsa-private.pem", "rb") as key_file:
        private_key = load_pem_private_key(
            key_file.read(), password=None, backend=default_backend()
        )
    signature = private_key.sign(message, ec.ECDSA(hashes.SHA1()))
    return signature

def generate_signed_url(url, key_pair_id, date_less_than):
    start_time = time.time()

    cf_signer = CloudFrontSigner(key_pair_id, ecdsa_signer)
    signed_url = cf_signer.generate_presigned_url(
        url,
        date_less_than=datetime.fromisoformat(date_less_than.replace("Z", "+00:00")),
    )

    processing_time_ms = (time.time() - start_time) * 1000

    print(f"# ECDSA processing time: {processing_time_ms:.2f}ms", file=sys.stderr)
    print(f"# ECDSA URL length: {len(signed_url)} characters", file=sys.stderr)
    return signed_url

if __name__ == "__main__":
    url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/file.jpg"
    expiry_time = sys.argv[2] if len(sys.argv) > 2 else "2025-09-10T12:09:03Z"
    key_pair_id = os.environ.get("ECDSA_KEY_ID")

    signed_url = generate_signed_url(url, key_pair_id, expiry_time)
    print(signed_url)
RSA 署名付き URL 生成
# rsa_signer.py
import sys
import time
from botocore.signers import CloudFrontSigner
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.backends import default_backend
import os
from datetime import datetime

def rsa_signer(message):
    with open("rsa-private.pem", "rb") as key_file:
        private_key = load_pem_private_key(
            key_file.read(), password=None, backend=default_backend()
        )
    signature = private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
    return signature

def generate_signed_url(url, key_pair_id, date_less_than):
    start_time = time.time()

    cf_signer = CloudFrontSigner(key_pair_id, rsa_signer)
    signed_url = cf_signer.generate_presigned_url(
        url,
        date_less_than=datetime.fromisoformat(date_less_than.replace("Z", "+00:00")),
    )

    processing_time_ms = (time.time() - start_time) * 1000

    print(f"# RSA processing time: {processing_time_ms:.2f}ms", file=sys.stderr)
    print(f"# RSA URL length: {len(signed_url)} characters", file=sys.stderr)
    return signed_url
if __name__ == "__main__":
    url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/file.jpg"
    expiry_time = sys.argv[2] if len(sys.argv) > 2 else "2025-09-10T12:09:03Z"
    key_pair_id = os.environ.get("RSA_KEY_ID")

    signed_url = generate_signed_url(url, key_pair_id, expiry_time)
    print(signed_url)

動作確認

それぞれの Python コードを実行して、署名付き URL が生成できれば、問題なく設定ができています。

expiry_time=$(date -u -d '+1 minute' '+%Y-%m-%dT%H:%M:%SZ')

echo "# Generating RSA signed URL for CloudFront..."
rsa_signedurl=$(python3 rsa_signer.py "https://example.com/private-files/test.jpg" "$expiry_time")
echo "# RSA Signed URL: $rsa_signedurl"

echo "# Generating ECDSA signed URL for CloudFront..."
ecdsa_signedurl=$(python3 ecdsa_signer.py "https://example.com/private-files/test.jpg" "$expiry_time")
echo "# ECDSA Signed URL: $ecdsa_signedurl"

性能測定の実施

署名付き URL 生成の動作確認ができたら、性能測定を行ないます。

環境設定

途中で取得した以下の変数が未設定であれば、設定します。

export RSA_KEY_ID="ZZZZZZZZZZZZZ"
export ECDSA_KEY_ID="WWWWWWWWWWWWWW"

性能測定スクリプト

性能測定を行なうために、別途 Python コードを用意して、以下のように実行しました。実行回数を1回、10 回、20 回と変えて実行します。

# 使用方法
expiry_time=$(date -u -d '+1 minute' '+%Y-%m-%dT%H:%M:%SZ')
python3 benchmark_signers.py \
    "https://example.com/file1.jpg" \
    "https://example.com/file2.jpg" \
    "$expiry_time" \
    100  # 実行回数

性能測定用のコード例

:
    def benchmark_ecdsa(
        self, url: str, expiry_time: str, iterations: int = 10
    ) -> Tuple[List[float], str]:
        """ECDSA署名付きURL生成のベンチマーク"""
        times = []
        signed_url = None

        cf_signer = CloudFrontSigner(self.ecdsa_key_pair_id, self.ecdsa_signer)
        date_less_than = datetime.fromisoformat(expiry_time.replace("Z", "+00:00"))

        for _ in range(iterations):
            start_time = time.perf_counter()
            signed_url = cf_signer.generate_presigned_url(
                url, date_less_than=date_less_than
            )
            end_time = time.perf_counter()
            times.append((end_time - start_time) * 1000)  # ミリ秒に変換

        return times, signed_url
:

RSA と ECDSA の平均処理速度比較

測定回数 RSA 平均 ECDSA 平均 改善率 RSA 標準偏差 ECDSA 標準偏差
1 回 36.47ms 3.04ms 91.7% 0.00ms 0.00ms
10 回 35.93ms 3.00ms 91.6% 2.54ms 0.31ms
20 回 33.54ms 2.60ms 92.2% 0.98ms 0.21ms
50 回 34.82ms 2.82ms 91.9% 1.73ms 0.58ms
100 回 34.39ms 2.91ms 91.5% 1.33ms 0.58ms

結果サマリー

  • ECDSA を使用した署名付き URL 生成は RSA に比べて約 12 倍高速(平均 91.6%の改善)
  • URL 長は約 55.1%短縮(RSA: 448 文字 → ECDSA: 201 文字)
  • 安定した性能(標準偏差が小さい)

この結果から、ECDSA が特に有効なケースは次のような場合だと考えられます。

  • 高頻度な URL 生成: API で大量の署名付き URL を生成する場合
  • モバイル/IoT デバイス: 計算リソースが限られた環境
  • URL 長制限: SMS、QR コード等で URL 長に制約がある場合
  • レスポンス時間重視: リアルタイム性が要求される用途

実際の署名付き URL 例

RSA 署名 URL(448 文字)

https://example.com/private-files/uploadtest.jpg?Expires=1757511063&Signature=TNGmM2jrc5GRTalKgYhdZla1uaR~gOiX4HevKDMBbe5JkecwxGe0VoCqXB5KnCPgetRbbBE6JCtOdCYiTwGYxKpByOBJTMaS5Z3H9rqhln~wqQ0KZ-q5-lqvEPsxXlbBXbzJ0MDwNgzcCcu3GavsYGiceFr0veGw5RuYNLSs49HdFUKtdk~7wpI~bjdHzLDSaxoTKtIz0HDgii~VTPP769X~aOmG7~W0hm23nBP4WcTDe6Z8YvDRr61TqjixkNYrh4FiuUFFVuA2Tn0FUuaxcU5qVhtcl0ng8QqoyHUwWFju65h6g0TUM2iueJudQtV-CcoYf-UHlVOGQ39pnG9HxQ__&Key-Pair-Id=ZZZZZZZZZZZZZ

ECDSA 署名 URL(201 文字)

RSA で生成した署名付き URL に比べて、247 文字も短い結果となりました。

https://example.com/private-files/uploadtest.jpg?Expires=1757511063&Signature=MEQCIBO~LD6qEaVjiwGFFF233dOB~XJvi7SPhubaN0FjFsigAiBlLZAYz9sCp6aY93bE9z0SuZDhEH3Pw8uL-9rPODVbQw__&Key-Pair-Id=WWWWWWWWWWWWWW

まとめ

Amazon CloudFront の署名付き URL での ECDSA サポートにより、大幅な性能向上と URL 短縮が実現されました。特に高負荷なシステムや計算リソースに制約がある環境の場合、大きな恩恵を受けるのではないでしょうか。

参考資料

Discussion