Webの証明書やパスキーの署名に使われる楕円曲線P-256は、1997年8月16日01時52分34秒(UTC)を指していたコンピュータの時計から出た数字で作られていました。NSAが出どころを明かさず、長くバックドアが疑われてきた数字です。10月16日にAnthropicが研究記事「Claude reveals ECDSA curve origins」でこの答えを示し、私もその計算を自分で再現して同じ数字を得ました。同じ週のHacker Newsでは、その曲線で署名するパスキーが「罠」かどうかで議論が起きていました。2つの話をつなげて読みました。
説明なしに渡された数字
ECDSAは、いまもっとも広く使われている楕円曲線署名の方式です。米国NISTは2000年のFIPS 186-2で、大きさの違う5つの曲線(P-192・P-224・P-256・P-384・P-521)を標準にしました。曲線を決める係数bは、「シード」と呼ばれる20バイトの数字をSHA-1でハッシュして得ます。ハッシュは逆にたどれないので、狙ったbが出るように数字を細工するのは難しい、という理屈です。問題はシードそのものでした。NSAが、どう選んだのかを説明しないまま渡した値だったのです。P-256のシードはこうなっています。
C49D3608 86E70493 6A6678E1 139D26B7 819F7E90疑いは長く続きました。Neal KoblitzとAlfred Menezesは、2015年の論文「A Riddle Wrapped in an Enigma」でこの論争を整理しています。Daniel BernsteinとTanja Langeは、シードを70ビットほど自由に選べれば、ほかの人が知らない弱点を持つ曲線を選び出せることを示しました。シードを作ったNSAの研究者Jerry Solinasは、冗談めいた英語の文をハッシュしたと記憶していましたが、その文を最後まで思い出せず、2023年に亡くなりました。同じ年、暗号学者のFilippo Valsordaが、シードの出どころを突き止めた人に12,288ドルの懸賞金をかけました。賞金を慈善団体に寄付するなら、3倍の36,864ドルに増やすという条件付きでした。
そんな昔の記録も見つからなかった。残っているとしたら、きっと聖櫃の隣のハードディスクに眠っているのだろう。
Jerry Solinas、シードを作ったNSAの研究者(Anthropicの記事より)
30分足らずで出た答え
2026年10月、Anthropicの社員が社内の研究用モデルに、このシードがどう作られたのかを尋ねました。「文をハッシュした」という手がかりは、あえて伝えませんでした。Claudeは30分足らずで、NISTの素体曲線5つのシードの出どころを突き止めました。文をハッシュしたことは一度もありませんでした。シードは、1990年代のCライブラリに入っていた乱数生成器lrand48をUnix時刻で初期化し、そこから取り出した値だったのです。
たどった道筋はこうです。Claudeは、曲線を作ったプログラムが当時よく使われていた擬似乱数生成器を使ったのだろうと見当をつけました。そうした生成器は弱く、出力を見るだけで見分けられるからです。シードを1ビットずつずらしながら古い生成器の連続した出力かどうかを調べるうちに、2進体曲線のシード4つにパターンを見つけました。4つのシードを成す32ビットのワード20個の最上位ビットが、すべて0だったのです。48ビットの状態のうち上位31ビットだけを返すlrand48の痕跡です。隠れた17ビットに入りうる2¹⁷通りの値をすべて試すと、続くワードが予測どおりに現れました。
素体曲線のシードには出力の一部(lrand48() >> 23)しか含まれていないため、格子簡約(LLL)で生成器の状態を復元しました。この生成器は逆向きにも簡単に回せます。状態をさかのぼり、srand48が最初に作る形(下位16ビットが0x330E)にたどり着くと、ANSI X9.62の例示曲線も含めた素体曲線のシード10個は、すべて1997年5月から1998年4月までのUnix時刻を指していました。2進体曲線の4つは1984年2月を指していましたが、2倍すると1998年4月17日になります。プログラムが時刻を半分にして使ったようです。

2進体曲線のシード4つを32ビットのワード20個に分け、ビットごとに塗りました。
最上位ビットが20個すべて0です。偶然こうなる確率は、およそ100万分の1です。

P-256のシードから生成器の状態を復元し、逆に回しました。
最初の状態の下位16ビットはsrand48が付ける0x330Eで、上位32ビットはUnix時刻871,696,354、1997年8月16日01:52:34(UTC)です。

さかのぼった時刻を並べると、すべて1997年5月から1998年4月までに収まります。
P-256は1997年8月16日に作られました。2進体曲線の4つは1998年4月17日に数分おきに作られています。
驚いた。25年来の謎が解けた。うーん、待て。疑ってみよう。自分は何かのシミュレーションかテスト環境の中にいるのか?
Claudeの思考過程、Anthropicの記事より
自分で動かしてみた
Anthropicが公開したコードを動かしたうえで、同じ計算を短く書き直してみました。P-256が指す時刻、1997年8月16日01:52:34(UTC)はUnix時刻で871,696,354です。日本時間では同じ日の午前10時52分34秒にあたります。この値で生成器を初期化し、20バイトの候補を順に取り出すと、1,842番目の候補がP-256のシードと1桁も違わずに一致します。
from datetime import datetime, timezone
import hashlib
t = int(datetime(1997, 8, 16, 1, 52, 34, tzinfo=timezone.utc).timestamp()) # 871696354
x = (t << 16) | 0x330E # srand48(t)が作る状態
seed = b""
for i in range(20 * 1842): # 20バイトの候補を1,842個
x = (0x5DEECE66D * x + 0xB) % 2**48 # lrand48を1ステップ
if i >= 20 * 1841: # 最後(1,842番目)の候補だけを集める
seed += bytes([x >> 40]) # lrand48() >> 23 = 状態の上位8ビット
print(seed.hex().upper()) # C49D360886E704936A6678E1139D26B7819F7E90
# ANSI X9.62 / FIPS 186の検証:シード → SHA-1 → c、そしてb²·c ≡ −27 (mod p)
p = 2**256 - 2**224 + 2**192 + 2**96 - 1
b = 0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
z = int.from_bytes(seed, "big")
w0 = int.from_bytes(hashlib.sha1(seed).digest(), "big") % 2**95
w1 = int.from_bytes(hashlib.sha1(((z + 1) % 2**160).to_bytes(20, "big")).digest(), "big")
c = (w0 << 160) | w1
print((b * b * c + 27) % p == 0) # Trueシードが一致するだけでは足りないので、規格が定める検証もしてみました。ANSI X9.62とFIPS 186では、シードをSHA-1でハッシュしてcを作り、曲線の係数bが次の式を満たすかを確かめます。規格に載っているP-256のbを入れると真になります。P-192・P-224・P-384・P-521も同じ方法ですべて一致しました。Filippo Valsordaが2023年の懸賞の記事に載せたシードの一覧とも、1文字まで同じです。

解けていない部分も残っています。P-256は1,842番目の候補でしたが、同じ実行の876番目の候補も条件を満たす曲線でした。なぜ飛ばしたのかは誰にもわかりません。Valsordaも検証してみたところ、使えるシードは実際に載ったものの2倍ほどあったと書いています。2進体曲線B-163のシードはいまも説明がつかず、P-224・P-256・P-384・P-521のベースポイントがどこから来たのかもまだわかっていません。
では、バックドアはあったのか
Anthropicは、今回の発見は暗号を破ったものでも新しい攻撃でもないと強調しています。むしろ、曲線にバックドアがないという確信が強まったといいます。HNでは反論も出ました。時刻や候補の番号を変えながら、弱い曲線が出るまで回したのかもしれない、というものです。懸賞を出したValsordaは、懸賞の決着を伝える記事でこう計算しました。NSAが1997〜98年のどの時刻を使ってもよく、最初に出た使える候補16個のどれを取ってもよかったとしても、選択の余地は30ビットほどです。BernsteinとLangeの手法に必要だった70ビットには遠く及びません。
あるかもしれない弱い曲線の一群を狙うにも、自分たちだけが開けるバックドアを仕込むにも足りない。
Filippo Valsorda、懸賞を出した暗号学者
HNのtptacekは「壊滅的なニュースだ……一世代にわたるNIST暗号の陰謀論にとっては」と書きました。Valsordaは、いまなら個人的にはCurve25519系よりNISTの曲線を勧めると述べています。ただ、汚名をすすいだばかりのNISTの曲線も、まもなく耐量子署名に席を譲って退くところだ、とも付け加えています。懸賞金の36,864ドルは、Let’s Encryptを運営する非営利団体ISRGに寄付されました。
同じ週、パスキーをめぐる論争
この曲線はパスキーの土台にもなっています。パスキーを定めるW3CのWebAuthn仕様は、サイトが署名アルゴリズムを指定しない場合、ES256(P-256上のECDSA)とRS256を既定にしています。そのパスキーについて、10月11日のHNに「Passkeys are a trap. Do not use them.」が投稿され、1,146ポイントを集めました。筆者(The Hack)は、パスキーには「隠れたダイヤル」があると書いています。どの認証器で作った鍵かを証明するアテステーションをサイトが少しずつ求め始めれば、承認された機器とOSでなければログインできなくなる、というのです。代わりにTOTP(認証アプリの6桁のコード)を勧めています。

3日後、セキュリティ研究者のScott Helmeが「Passkeys are not a trap」で反論しました(HN 672ポイント)。コメントは814件で、元の記事(577件)より多くなりました。Helmeの要点はこうです。多くの人が使うiCloudキーチェーン・Googleパスワードマネージャー・1Password・Bitwardenの同期パスキーは、アテステーションを返しません。サイトが求めても、返ってくる値はnoneです。アテステーションがあるときに伝わるのも認証器のメーカーとモデルだけで、OSやブラウザ、root化の有無は伝わりません。そしてTOTPのコードは偽サイトにもそのまま入力できてしまいますが、パスキーは作成したドメインでしか使えません。


フィッシングに強い認証情報を、フィッシングされやすいものと取り替えるのは、本当にまずい取引だ。
Scott Helme, Passkeys are not a trap
曲線の疑いは晴れ、残るはルールの問題
私の見方では、2つの話は同じ問いの表と裏です。P-256をめぐる長い疑いは、誰でも検証できる計算で決着しました。パスキーをめぐる論争は、数学ではなく誰が鍵とルールを握るかの問題です。Helmeも、アテステーションが一般向けのログインに広がるなら声を上げて反対すべきだと書いています。パスキーを導入する開発者なら、次の点は押さえておきたいところです。
アルゴリズムの一覧にES256(-7)を入れます。WebAuthn仕様は、幅広い認証器を受け入れたいサイトにEdDSA(-8)・ES256(-7)・RS256(-257)を含めるよう勧めています。
アテステーションは本当に必要なときだけ求めます。一般向けのサービスではアテステーションを求めないという指針がもう何年も続いている、とHelmeは書いています。
パスキーは信頼できるパスワードマネージャーに保存します。複数の機器やプラットフォームを行き来するなら、パスキーを持ち出せるサービスを選ぶ、というのもHelmeの勧めです。
25年以上、その数字がどこから来たのかは誰も知らず、人々は数字の裏に誰がいるのかを疑ってきました。そこにあったのは1997年8月の時計でした。そして疑いを終わらせたのは、誰でも再現できる計算でした。