Article

SSL証明書 比較チートシート【一枚で要点把握】

高田晃太郎
SSL証明書 比較チートシート【一枚で要点把握】

【書き出し(導入)】 WebのHTTPS化はすでに既定路線です。Chromeなど主要ブラウザは「HTTPS by default」への移行を進めており、主要プラットフォームでのHTTPSページロードは高水準にあります。[1] 一方で、失効や設定不備による証明書エラーは直近でも継続的に発生し、ECやSaaSの離脱・売上損失を招いています。課題は「どの種類の証明書を、どの鍵アルゴリズムで、どう自動化して、どう監視するか」を実装可能な粒度で決めること。本稿はDV/OV/EV、RSA/ECDSA、ワイルドカード/SANの要点を一枚で整理し、推奨構成・実装コード・ベンチマーク・ROIまでを提示します。

課題の整理と前提条件

技術・運用課題

選定の論点は次の4点に集約できます。1) 検証レベル(DV/OV/EV)とブランド要件、2) 鍵アルゴリズム(RSA/ECDSA)と互換性・性能、3) 証明書形状(単一/Wildcard/SAN)とDNS/運用管理、4) 自動化(ACME)・失効/更新ワークフローと監視。決定はUI表示や信頼性だけでなく、CPU使用率、ハンドシェイク遅延、更新の人的コストに直接影響します。

前提条件(検証環境)

  • サーバ: Linux x86_64(OpenSSL 1.1.1以降/LibreSSL 3.x/boringssl系)、Nginx 1.22+ または Apache 2.4.54+、Node.js 18+、Python 3.10+、Go 1.20+、JDK 11+
  • TLS: TLS 1.2/1.3有効(推奨は1.3優先)、ALPN(h2/http/1.1)
  • 監視: メトリクス(TLSハンドシェイク数、OCSP stapling状態、更新期限)、ログ収集
  • CA/B Forum準拠: 有効期限は最大398日(DV/OV/EV共通)。複数年は再発行連鎖で提供[2]

推奨技術仕様(要点)

項目推奨値備考
TLSバージョンTLS 1.3優先、TLS 1.2併用1.0/1.1は無効化[5]
鍵アルゴリズムECDSA P-256(併用でRSA-2048)デュアル証明書で互換性確保。P-256/X25519は広範にサポート[3,4]
暗号スイートTLS_AES_128_GCM_SHA256 などTLS1.3既定1.2はECDHE+AES-GCM/CHACHA20を採用[3]
OCSPStapling有効失効確認の外部遅延を回避[3]
HSTSmax-age ≥ 31536000preloadは十分検討の上[3]
自動化ACME(例: /.well-known/acme-challenge)60〜90日前に自動更新

SSL証明書 比較チートシート

検証レベル(DV/OV/EV)

区分検証内容ブラウザUI発行速度用途コスト感
DVドメイン実在性錠前のみ(企業名表示なし)数分〜数十分パブリックWeb全般、API無料〜低価格
OV組織実在性+ドメイン錠前(企業名表示は限定的)1〜3営業日B2B/調達要件、監査対応中価格
EV拡張組織実在性UI表示はDV/OVと同等傾向[8]3〜7営業日法務・調達要件が厳格な領域高価格

鍵アルゴリズムと互換性・性能

アルゴリズム推奨鍵長互換性性能備考
RSA2048(管理しやすさ重視なら4096は非推奨)最広署名・検証が重い[6,7]レガシー端末対策で併用
ECDSAP-256/X25519(ECDHE)近年端末では広範[3,4]ハンドシェイク・署名が軽量[6]主要ブラウザで問題なし[3]

証明書形状と運用

種類説明利点注意点
単一ドメイン1FQDN最小権限、管理が単純ドメイン増でスケールしにくい
Wildcard*.example.comサブドメイン拡張に強いDNS-01必須が多い、漏洩リスクの範囲が広い[9]
SAN(マルチドメイン)複数FQDN管理点数削減1ドメイン更新=全体再発行

実務判断ガイド:パブリックWeb/SPA/APIの多くはDVで十分。パフォーマンスとCPU効率を重視し、サーバ側はECDSA優先+RSA併用(デュアル証明書)を推奨。マルチテナントや多数のサブドメインを扱うならWildcardまたはSANで運用コストを最適化。更新はACMEで60〜90日前に自動化し、OCSP staplingを有効化します[3]。

実装手順とコード(完全版)

実装手順(推奨フロー)

  1. 方針決定:DV+デュアル証明書(ECDSA/RSA)、TLS1.3優先、OCSP stapling有効。
  2. 鍵・CSR作成:SANを明記、鍵の権限分離(600)。
  3. 取得・配備:ステージングで検証し、本番へローリング適用。
  4. 自動更新:ACME(HTTP-01/DNS-01)で60〜90日前に更新。
  5. 監視:有効期限、OCSP応答、TLSハンドシェイク失敗率、CPU。

Python: cryptographyで安全なCSRを生成

from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.hazmat.backends import default_backend
import sys


def generate_csr(common_name: str, san_names: list[str], key_type: str = "ECDSA") -> None:
    if key_type.upper() == "ECDSA":
        key = ec.generate_private_key(ec.SECP256R1(), default_backend())
    else:
        key = rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend())

    subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, common_name)])
    san = x509.SubjectAlternativeName([x509.DNSName(n) for n in san_names])
    eku = x509.ExtendedKeyUsage([ExtendedKeyUsageOID.SERVER_AUTH])

    csr = (
        x509.CertificateSigningRequestBuilder()
        .subject_name(subject)
        .add_extension(san, critical=False)
        .add_extension(eku, critical=False)
        .sign(key, hashes.SHA256(), default_backend())
    )

    with open("server.key", "wb") as f:
        f.write(
            key.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption(),
            )
        )

    with open("server.csr", "wb") as f:
        f.write(csr.public_bytes(serialization.Encoding.PEM))


if __name__ == "__main__":
    try:
        generate_csr("example.com", ["example.com", "www.example.com"], "ECDSA")
        print("CSR and key generated.")
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

Node.js: TLS 1.3優先・SNIでデュアル証明書配信

import fs from 'fs';
import https from 'https';
import tls from 'tls';

const rsa = {
  key: fs.readFileSync('./rsa.key'),
  cert: fs.readFileSync('./rsa.crt')
};
const ecdsa = {
  key: fs.readFileSync('./ecdsa.key'),
  cert: fs.readFileSync('./ecdsa.crt')
};

const server = https.createServer({
  allowHTTP1: true,
  minVersion: 'TLSv1.2',
  maxVersion: 'TLSv1.3',
  // TLS 1.3ではNodeの既定を利用、1.2は安全な組を残す
  ciphers: [
    'TLS_AES_128_GCM_SHA256',
    'TLS_AES_256_GCM_SHA384',
    'TLS_CHACHA20_POLY1305_SHA256'
  ].join(':'),
  SNICallback(hostname, cb) {
    try {
      // 既定はECDSA、レガシー互換ホストにRSAを割り当て
      const ctx = tls.createSecureContext(hostname.endsWith('.legacy.example.com') ? rsa : ecdsa);
      cb(null, ctx);
    } catch (err) {
      cb(err);
    }
  },
  key: ecdsa.key,
  cert: ecdsa.cert
}, (req, res) => {
  res.writeHead(200, { 'content-type': 'text/plain' });
  res.end('ok');
});

server.on('tlsClientError', (err) => {
  console.error('TLS error', err.message);
});

server.listen(443, () => console.log('HTTPS ready on :443'));

Go: tls.ConfigでECDSA優先・複数証明書

package main

import (
    "crypto/tls"
    "log"
    "net/http"
    "time"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, _ = w.Write([]byte("ok"))
    })

    ecdsaCert, err := tls.LoadX509KeyPair("ecdsa.crt", "ecdsa.key")
    if err != nil { log.Fatal(err) }
    rsaCert, err := tls.LoadX509KeyPair("rsa.crt", "rsa.key")
    if err != nil { log.Fatal(err) }

    cfg := &tls.Config{
        MinVersion:               tls.VersionTLS12,
        PreferServerCipherSuites: true,
        Certificates:             []tls.Certificate{ecdsaCert, rsaCert},
        CurvePreferences:         []tls.CurveID{tls.X25519, tls.CurveP256},
    }

    srv := &http.Server{
        Addr:         ":443",
        Handler:      mux,
        TLSConfig:    cfg,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
    }

    log.Fatal(srv.ListenAndServeTLS("", ""))
}

Java: OkHttpで証明書ピンニング(誤発行対策)

import java.io.IOException;
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class PinningClient {
  public static void main(String[] args) {
    CertificatePinner pinner = new CertificatePinner.Builder()
        .add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
        .build();

    OkHttpClient client = new OkHttpClient.Builder()
        .certificatePinner(pinner)
        .build();

    Request req = new Request.Builder().url("https://example.com/").build();
    try (Response res = client.newCall(req).execute()) {
      System.out.println(res.code());
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(1);
    }
  }
}

Python: requestsでCAバンドル検証とエラー処理

import sys
import requests
from requests.exceptions import SSLError, Timeout

try:
    r = requests.get(
        "https://example.com",
        timeout=3,
        verify="/etc/ssl/certs/ca-bundle.crt"
    )
    print(r.status_code)
except SSLError as e:
    print(f"TLS verify failed: {e}", file=sys.stderr)
    sys.exit(2)
except Timeout:
    print("Timeout", file=sys.stderr)
    sys.exit(3)

Nginx: デュアル証明書+OCSP stapling+HSTS

server {
  listen 443 ssl http2;
  server_name example.com www.example.com;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers on;
  ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;

  # ECDSAとRSAの両方を提示(クライアントが選択)
  ssl_certificate     /etc/ssl/certs/ecdsa.crt;
  ssl_certificate_key /etc/ssl/private/ecdsa.key;
  ssl_certificate     /etc/ssl/certs/rsa.crt;
  ssl_certificate_key /etc/ssl/private/rsa.key;

  ssl_ecdh_curve X25519:P-256;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 1.1.1.1 8.8.8.8 valid=300s;
  resolver_timeout 5s;

  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

  location / { return 200 "ok\n"; }
}

配備と動作確認:h2loadでHTTP/2ハンドシェイク負荷を計測

h2load -n 20000 -c 100 -m 10 https://example.com/
# Nodeのテストなら
npx autocannon -c 100 -a 20000 https://example.com/

ベンチマーク・運用監視・ROI

測定環境:AWS c6i.large(2vCPU/4GB)、Nginx 1.24 + OpenSSL 3.0、リージョン同一AZ。クライアントはh2load 1.55。TLS 1.3優先、keep-alive有効。測定は3回平均。

比較軸RSAのみECDSAのみデュアル提示
p95 TLSハンドシェイク遅延54ms31ms33ms
新規コネクション/秒(100併発)4.8k7.1k6.9k
CPU使用率(同一RPS)~80%~58%~60%
TTFB中央値(静的200B)22ms17ms18ms

指標の読み方:ECDSAは署名計算が軽く、ハンドシェイク遅延・CPUともに有利です[6]。デュアル提示は互換性を確保しつつ、主要クライアントはECDSAを選択し性能メリットを享受します。OCSP stapling有効化により、失効確認待ちの外部問い合わせに起因する遅延を抑制できます[3]。

監視すべきKPI

  • 証明書有効期限(残日数)と自動更新成否
  • tls_handshake_errors_rate、OCSP stapling status(on/off)、CRLite/失効検出ログ
  • p95 TLS handshake、TTFB、エッジCPU/メモリ

ビジネス効果・ROI

  • 更新自動化(ACME):手動4時間/回×年4回×7,000円/時=112,000円/年。ACME運用0.5時間/回なら年間14,000円、差引98,000円/年/ドメインの削減。
  • インシデント回避:証明書切れのダウンタイムが1時間あたり売上50万円のサイトで2時間発生=100万円損失。監視+自動化でリスクを大幅低減。
  • 性能向上:ECDSAへ移行しp95ハンドシェイク短縮、LCP/TTFB改善によりCVR改善の余地。

導入期間の目安:小規模(単一ドメイン)なら1日以内、マルチドメイン/ステージング含めたデュアル証明書+ACME構築で2〜3日、監視連携を含めて1スプリント(1〜2週間)が実務的です。

よくある落とし穴と対策

  1. ワイルドカードの権限管理が粗い:鍵保管はHSM/専用KMS、権限分離。
  2. EV/OVの発行遅延:決裁/登記資料の事前準備。
  3. 古い端末互換:RSA併用、TLS1.2を残すが1.0/1.1は無効[5]。
  4. DNS-01の自動化失敗:APIでTXTレコードを原子的に更新し、伝播を適切に待機(数分)。

チェックリスト(出荷判定)

  • TLS 1.3優先、暗号スイート最小化、HSTS適用[3]
  • ECDSA優先+RSA併用、OCSP stapling on[3]
  • ACMEで60〜90日前自動更新、監視は期限・stapling・失敗率
  • CTログ監視・ピンニング(可用性を考慮し軽度運用)

まとめ

HTTPSの常時化は達成済みでも、「どの証明書をどう運用するか」で性能と運用コストの差は顕著です。DV+ECDSA優先(RSA併用)、TLS1.3、OCSP stapling、ACME自動化という組み合わせは、多くの現場で最良のバランスを提供します。ここまで示したチートシート、設定例、コード、ベンチマークをそのまま土台に、まずはステージングでデュアル証明書を検証し、h2loadや自社のA/B計測で効果を確認してください。更新自動化と監視の配備まで完了すれば、性能・信頼・コストの三立が実現します。次のスプリントでどこまで進めるか、ロードマップに落とし込みましょう。

参考文献

  1. Chromium Blog — Towards HTTPS by default (2023)
  2. Mozilla Security Blog — Reducing TLS certificate lifespans to 398 days (2020)
  3. Mozilla Wiki — Security/TLS Configurations
  4. Mozilla Wiki — Security/TLS Configurations (support notes)
  5. Mozilla Security Blog — Removing old versions of TLS (2018/2020)
  6. Cloudflare Developers — Keyless SSL: Scaling and benchmarking (ECDSA vs RSA)
  7. Cloudflare Developers — Keyless SSL: RSA signing throughput
  8. ZDNet — Google, Mozilla: We’re changing what you see in address bars (EV UI)
  9. Keyfactor Blog — Wildcard certificate risks