TLS / 暗号

通信を盗聴・改竄・なりすましから守る仕組み。HTTP の上に TLS が乗ると HTTPS になる。 対称鍵暗号 / 公開鍵暗号 / 証明書 / ハンドシェイクまで。

SSL と TLS

TLS が提供する 3 つのこと

  1. 機密性(Confidentiality)— 盗聴されても読めない
  2. 完全性(Integrity)— 改竄されたら検出
  3. 認証(Authentication)— 通信相手が本物であることを確認

暗号の基礎

対称鍵暗号

公開鍵暗号(非対称)

ハイブリッド方式

実運用では「公開鍵暗号で対称鍵を交換 → 以降は対称鍵で通信」のハイブリッド。TLS もこれ。

ハッシュ関数

HMAC / AEAD

鍵交換 (Key Exchange)

Forward Secrecy

サーバの秘密鍵が将来漏れても、過去の通信は復号できない性質。 現代の TLS では (EC)DHE で毎セッション一時鍵を作るので確保される。

証明書 (X.509)

サーバが「私は example.com です」と第三者の保証付きで示すための電子証明書。

含まれるもの

SAN(Subject Alternative Name)

現代の証明書はCN ではなく SAN でドメインを指定。 example.comwww.example.com を 1 枚で扱う、ワイルドカードなど。

CA(認証局)

証明書を発行する第三者機関。OS / ブラウザにルート CA の証明書が組み込まれているのが起点。

主要 CA

証明書の種類

種類検証レベル取得
DVドメイン所有確認のみ無料・即発行
OV組織情報も検証有料・数日
EV厳格な実体確認(緑バー時代)高価・数週間

現代のブラウザは EV を視覚的に区別しないため、DV で十分なことが多い。

信頼の連鎖

ルート CA(OS/ブラウザに組込)
   |
中間 CA(CA が運用)
   |
リーフ証明書(example.com の証明書)
      

サーバはリーフ + 中間を一緒に提示する必要がある(ルートはクライアントが持っている)。

TLS 1.3 ハンドシェイク(簡略)

Client                              Server
   |--- ClientHello ----------------->|
   |     - 対応暗号スイート                |
   |     - 鍵交換用の公開鍵 (X25519)         |
   |                                  |
   |<-- ServerHello ------------------|
   |     - 選んだ暗号スイート              |
   |     - サーバの公開鍵                |
   |     - 証明書(暗号化済)             |
   |     - Finished                  |
   |                                  |
   |--- Finished ------------------->|
   |                                  |
   |== 以降は対称鍵で暗号化された通信 ==|
      

SNI(Server Name Indication)

クライアントが「これからアクセスするドメイン名」を ClientHello に含める拡張。 1 IP / 複数ドメインのサーバが、適切な証明書を返せるようになる。

ESNI / ECH

SNI は平文なので「どのドメインに繋ぐか」が中間者に見える問題があった。 Encrypted Client Hello (ECH) で SNI も暗号化されつつある(Cloudflare / Firefox 等)。

OCSP / CRL

証明書の失効確認

HSTS

「以降このドメインは HTTPS 必須」とブラウザに教えるヘッダ。HTTP への降格攻撃を防ぐ。

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

HSTS Preload List に登録するとブラウザに最初から組込まれる(Chrome / Firefox / Edge / Safari)。

クライアント認証 (mTLS)

通常はサーバだけ証明書を提示するが、mTLS ではクライアントも証明書を提示する。 社内 API / IoT / Zero Trust ネットワークで使用。

暗号スイート

TLS で使う暗号アルゴリズムの組み合わせ。

TLS_AES_128_GCM_SHA256              ← TLS 1.3
└─┘└─────────┘└────┘
TLS / AEAD     / ハッシュ
      

Let's Encrypt の使い方

# certbot
sudo certbot certonly --standalone -d example.com -d www.example.com

# ACME クライアント (acme.sh)
acme.sh --issue -d example.com --webroot /var/www

# 自動更新(cron)
0 3 * * * /usr/bin/certbot renew --quiet

多くのプラットフォーム(Cloudflare / Vercel / Netlify / Cloudflare Pages)は自動 TLS。 意識せずに HTTPS が動く。

OpenSSL でデバッグ

# サーバの証明書を見る
openssl s_client -connect example.com:443 -servername example.com

# 証明書の中身
openssl x509 -in cert.pem -text -noout

# 自己署名証明書を作る(開発用)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
  -days 365 -nodes -subj "/CN=localhost"

# CSR を作る
openssl req -new -newkey rsa:2048 -nodes -keyout key.pem -out req.csr

HTTPS 化のメリット

その他の暗号通信

WebCrypto API

ブラウザで暗号操作ができる標準 API。

// SHA-256 ハッシュ
const buf = new TextEncoder().encode("hello")
const hash = await crypto.subtle.digest("SHA-256", buf)
const hex = Array.from(new Uint8Array(hash))
  .map(b => b.toString(16).padStart(2, "0")).join("")

// AES-GCM 暗号化
const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
)
const iv = crypto.getRandomValues(new Uint8Array(12))
const enc = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  key,
  buf
)

失敗パターン

症状原因
NET::ERR_CERT_DATE_INVALID有効期限切れ / 端末時計ズレ
NET::ERR_CERT_AUTHORITY_INVALID自己署名 or 信頼されない CA
NET::ERR_CERT_COMMON_NAME_INVALIDSAN にドメインが入っていない
HSTS で繋がらない過去の HSTS が残っている。chrome://net-internals/#hsts で削除
中間証明書欠落サーバが fullchain を返していない
古い暗号スイートTLS 1.0 / 1.1 を使ってる

SSL Labs / 検査ツール

原則

現代の Web はHTTPS 一択。Let's Encrypt や CDN の自動 TLS で 実装コストはほぼゼロ。HSTS / OCSP Stapling / TLS 1.3 を有効にしておくのが基準ライン。