時刻は「参考値」ではありません
開発をしていると、NTP(Network Time Protocol)を「時計を合わせるだけのプロトコル」と軽く捉えがちです。しかし実際には、証明書検証、トークン有効期限、リプレイ攻撃の防止、ログの順序保証など、ほぼすべてのセキュリティ判断の基準値がこの時刻です。
問題は、NTPが1985年の設計以来認証機能を一切持っていない点にあります。クライアントが48バイト送信し、誰かが48バイト返し、クライアントはそれをそのまま信じます。署名も身元確認もありません。
X.509証明書の
notBefore/notAfterを検証するには現在時刻が必要で、現在時刻を信頼するには証明書検証が必要。これは完全な循環構造です。
以前は数秒のずれも運用上の不便程度でしたが、今は違います。CA/Browser ForumのSC-081v3によりTLS証明書の有効期間が2026年3月200日 → 2027年3月100日 → 2029年3月47日へ短縮されると、時計がずれたホストの自動更新ループは誰も見ていない深夜に静かに失敗します。
Metaはこの問題を解決するためnts.meta.comを公開し、サーバー・クライアント・プロトコル実装をすべてGitHubでオープンソース化しました。根拠資料はMeta Engineering Blogで確認できます。

NTSは2フェーズで動作します
Phase 1: NTS-KE(鍵交換)
TCP/4460上でTLS 1.3ハンドシェイクを行い、ALPNはntske/1を使用します。ここでAEADアルゴリズムを交渉し、RFC 5705 exporterでセッション鍵2つ(C2S、S2C)を導出します。サーバーはCookieを8個発行し、実際に接続すべきNTPサーバーを通知して接続を閉じます。パケットごとではなく一度だけです。
Phase 2: 認証されたNTP
UDP/123でNTPv4パケットを送信し、NTS拡張フィールドを付与します。リクエストには一意な識別子、Cookie、認証子が含まれます。サーバーはCookieを開いてセッション鍵を復元し、認証子を検証して応答します。偽造パケットは検証失敗で静かにドロップされ、NTS NAKも送信しないため、攻撃者がこの失敗を悪用してクライアントを強制的に再鍵交換させることはできません。
ステートレスCookie設計が鍵
通常Cookieというとサーバーが状態を保持する必要があると考えますが、Metaは鍵リングを複製しません。代わりにマスターシークレットと現在のepoch dayから封印鍵を導出します。
# Cookie封印鍵の導出(擬似コード)
# master: サーバー共有マスターシークレット
# day: Unix epoch基準の24時間単位整数(タイムゾーン/DSTなし)
import hmac, hashlib, struct
def derive_cookie_seal_key(master: bytes, day: int) -> bytes:
# HKDF-SHA256(master, salt=BE32(day), info="fbnts-cookie-seal-v1")
salt = struct.pack(">I", day)
prk = hmac.new(salt, master, hashlib.sha256).digest()
return hmac.new(prk, b"fbnts-cookie-seal-v1", hashlib.sha256).digest()
# サーバーはこの鍵でCookieをAES-SIVで開閉
# 鍵リング複製なし、セッションテーブルなし、共有状態なし
この設計により、KEサーバーとNTP応答サーバーが別マシンでも何もやり取りする必要がありません。サーバー追加はただのサーバー追加です。
chrony設定はたった一行
# /etc/chrony/chrony.conf
pool nts.meta.com nts iburst maxsources 5
nts.meta.comは鍵交換のみを担当し、ハンドシェイク中に実際のNTPレスポンダーを通知します。そのため設定したホストと実際に通信するホストが異なります。serverではなくpoolを使う理由は、単一障害点を避け、NTPが複数ソースで多数決できるようにするためです。
# chrony sources確認
chronyc -N authdata
# Name/IP address Mode KeyID Type KLen Last Atmp NAK Cook CLen
# time1.meta.com NTS 5 30 128 27 0 0 8 68
# time2.meta.com NTS 7 30 128 142 0 0 8 68
# time3.meta.com NTS 10 30 128 548 0 0 8 68
# time4.meta.com NTS 1 30 128 1177 0 0 8 68
# time5.meta.com NTS 2 30 128 1177 0 0 8 68
Type 30は交渉されたAEAD(AES-128-GCM-SIV)、KLen 128はセッション鍵長、CLen 68はCookieサイズ(36バイトのエンベロープ+16バイト鍵2つ)、Cook 8は保持Cookie数、NAK 0は拒否されたことがないことを意味します。

NTSが防げるもの・防げないもの
| 項目 | NTS対応 | 説明 |
|---|---|---|
| MITM時刻操作 | ✅ 防御 | 署名検証失敗パケットはドロップ。時計の巻き戻し/進み攻撃を遮断 |
| リプレイ攻撃 | ✅ 防御 | 一意な識別子の不一致でマッチ失敗 |
| クライアント追跡 | ✅ 防御 | Cookieが応答認証子内に暗号化され、交換ごとに異なるblob |
| 遅延攻撃(Delay) | ❌ 不可 | RFC 8915明記。パケット保留で最大遅延の半分まで時計移動可能 |
| ブートストラップ | ❌ 不可 | NTS-KEがTLS上で動作するため結局時計が必要(循環問題) |
| 誤ったサーバー | ❌ 不可 | 経路は認証するが答えの正確性は保証しない。クォーラム/監視必須 |
| 単調性(Monotonic) | ❌ 不可 | wall clockはうるう秒や補正で後退しうる。経過時間測定にはmonotonic clockを使用 |
本当の大きな問題はモバイルです
サーバー側はchrony、ntpsecが既にNTSを実装し、公開NTSサーバーリストもPTB、Netnod、time.nlなどかなりありますが、AndroidとiOSの標準時刻クライアントは依然として非認証SNTPです。
- Android: プラットフォーム時刻同期はUDP/123上のplain SNTP(固定48バイト、拡張フィールド処理なし)+ NITZ + GNSS。NTS経路なし。
- Apple:
timedがtime.apple.comと同期するがNTSサポートは確認されていません。
この2つのスタックが数十億台のスマホ・時計・ヘッドセットの時刻を設定しているのに、すべてが非認証UDPパケットを信頼しています。ベンダーがchronyを別途載せて回避できますが、プラットフォームが自ら行うべきことを回避しているに過ぎません。
2036年のNTP 32ビットラップ以降に時計がずれた機器は、TLS失敗 → 更新サーバー到達不可 → パッチ配布不可のデッドロックに陥ります。工場出荷時リセットかサービスセンター行きしかありません。フリート単位で考えれば、それはもはや物流問題です。

実務適用のためのまとめ
- サーバー運営者なら: 公開時刻サービスを運用しているならNTSを有効にしてください。Meta実装がGitHubにあります。
- NTPクライアント管理者なら: RFC 8915を実装するか、chrony/ntpsecへ移行してください。
- モバイルOS時刻スタック担当者なら: ここが本当のギャップです。Android/iOS両方ともまだ非認証パケットです。
- 一般バックエンド開発者なら: 経過時間測定には必ずmonotonic clockを使い、時刻補正が必要なロジックにはクォーラムとsanity limitを設けてください。
NTSは万能ではありません。経路は認証しますが答えの正確性は保証せず、遅延攻撃は防げず、ブートストラップ循環問題も残ります。 それでも「誰でもない何かがくれた時計」から「検証された送信者がくれた時計」へ移行するだけで、証明書更新自動化時代には十分意味のある前進です。