はじめに
インターネット通信の多くは暗号化されています。
「じゃあ暗号化されていれば検閲やトラフィック分類は無理なのでは?」と思いきや、実は 暗号化通信の「握手」部分 にはクライアントの癖が残っています。
そこで登場するのが DPI(Deep Packet Inspection: 深層パケット検査) と TLS 指紋識別です。
今回はその代表格である JA3 を中心に、仕組みや活用例、課題と対抗策をまとめます。
DPIとは?
- DPI は、単に IP やポート番号をチェックするだけではなく、パケット内容やプロトコルの構造 まで解析します。
- 暗号化通信であっても、TLS ハンドシェイク(特に ClientHello / ServerHello) の情報は平文でやりとりされるため、そこから「指紋」を抽出できます。
これを活用すれば:
- セキュリティ製品は マルウェアの検知 や Bot の遮断 に利用
- 国家レベルでは 通信検閲や特定アプリの識別 に利用
JA3 指紋とは?
基本の考え方
- TLS ClientHello に含まれる以下の情報を抽出:
- TLS バージョン
- サポート暗号スイートのリスト
- TLS 拡張の種類と順序
- サポートする楕円曲線とフォーマット
- これらを「固定順序で文字列化」し、
- MD5 ハッシュ を取ることで「JA3 フィンガープリント」として表現。
特徴
- 実装やライブラリごとに順序や組み合わせが異なるため、
ブラウザ、アプリ、ライブラリの違いが浮き彫りになる。 - サーバ側も同様に「JA3S」指紋を生成可能。
JA3 以外の指紋信号
-
SNI(Server Name Indication)
→ 接続先のドメイン名。暗号化されていなければ DPI が容易に取得可能。 -
証明書チェーン
→ 発行元 CA や中間証明書の癖を利用。 -
HTTP ヘッダの順序や User-Agent
→ TLS を越えてアプリ層で判別。 -
トラフィック統計(Flow Fingerprinting)
→ パケットサイズの並び方、間隔、方向などを特徴量に。
実装例(サンプル)
tshark で JA3 を計算
tshark -r sample.pcap -Y "ssl.handshake.type==1" -T fields \
-e ip.src -e ssl.handshake.extensions_server_name \
-e ssl.handshake.ciphersuites -e ssl.handshake.extension.type
Python で JA3 を計算(scapy + hashlib)
from scapy.all import rdpcap
import hashlib
packets = rdpcap("sample.pcap")
for p in packets:
if p.haslayer("TLSClientHello"):
fields = [
p["TLSClientHello"].version,
",".join(str(c) for c in p["TLSClientHello"].ciphers)
]
ja3_str = ",".join(fields)
ja3_hash = hashlib.md5(ja3_str.encode()).hexdigest()
print("JA3:", ja3_str, "→", ja3_hash)
限界と対抗策
限界
- TLS 1.3 + ECH(Encrypted ClientHello) の普及で、ClientHello の大部分が暗号化され、指紋が取りにくくなる。
- 同じライブラリを使う複数アプリ は区別できない(例: OpenSSL 系)。
- 拡張ランダム化 など指紋回避策が広まると精度低下。
対抗策(規制回避側)
- 主流ブラウザの指紋模倣
- obfs4 / meek などのトラフィック難読化
- パケット長・時間のランダム化
まとめ
- DPI は暗号化通信でも TLS ハンドシェイクの癖 を利用して識別可能。
- 代表的な技術が JA3/JA3S。
- SNI や証明書、統計的特徴も組み合わせれば識別精度が向上。
- しかし TLS1.3 + ECH の普及や難読化技術により、将来的にはこの手法の効果は限定的になる可能性大。
参考リンク
- Salesforce Research: Introducing JA3
- GFW Report: Geedge / MESA Leak 分析記事
- Wireshark / tshark Docs
TLS ハンドシェイク & 指紋抽出ポイント図
DPI の全体フロー(どこで“見る”のか)