Git 3.0のSHA-256デフォルト — ハッシュを替えるのか、信頼を設計し直すのか
Git 3.0のSHA-256デフォルト — ハッシュを替えるのか、信頼を設計し直すのか
SHA-1の弱さは、ハッシュアルゴリズムの交換で直すべき問題なのか、それともハッシュに信頼を預けない構造で迂回すべき問題なのか?
導入
2026年10月1日(UTC)、Hacker Newsのトップに「Git 3.0’s upcoming SHA-256 default will be a costly mistake」という記事が上がり、583ポイントと500件を超えるコメントを集めた。筆者はGitHubとGitButlerの共同創業者であり、『Pro Git』の著者でもあるScott Chaconだ。彼は冒頭から語気を強める。Git 3.0がSHA-256を新しいデフォルトハッシュにすることは「想像しがたいほど高くつき、結局は無価値で、避けられたはずの世界規模の悪夢」になる、と書く。
Gitプロジェクト側のドキュメントはまったく逆のトーンである。Gitの「Upcoming breaking changes」ドキュメントは、Git 3.0の最初の変更項目として新規リポジトリのデフォルトハッシュ関数をsha1からsha256に変えると記し、その根拠としてNISTが2011年にSHA-1を非推奨(deprecate)にしたこと、FIPS 140-2などの認証で推奨されていないこと、そして2015年から2020年にかけて続いた一連の実用的な攻撃を列挙する。「SHA-1が完全に破られたとみなされるのは時間の問題」なので、前もって備えるという論理だ。
二つの文書を並べると、論点は「SHA-1は弱いのか」ではない。その点では双方とも一致している。論点は、その弱さへの対応手段として「新規リポジトリのデフォルトを変える」というレバーが適切なのか、そしてそのコストを誰が払うのかである。本稿ではChaconの主張、Git公式の設計ドキュメント、Hacker Newsの反論を順に突き合わせ、この問いを検証する。
何が変わり、何が壊れるのか
まず事実関係を整理しておこう。Gitはコンテンツアドレッサブル(content-addressable)なデータベースである。ファイル、ディレクトリ(ツリー)、コミットのいずれも、内容のハッシュ値を名前として持つ。コミットは直前のコミットのハッシュを含むため、最新コミット一つのハッシュがそれ以前の履歴すべてを連鎖的に固定する。2005年にLinus Torvaldsがこのハッシュとして SHA-1 を選び、その選択が20年間維持されてきた。
Git公式ドキュメント「hash-function-transition」は、2017年2月23日にSHAttered攻撃が実用的なSHA-1衝突を実証したこと、そしてGit v2.13.0以降はSHAtteredに対して脆弱でない「強化版(hardened)SHA-1」実装をデフォルトで使っていることを明記している。それでも「SHA-1は依然として弱い」と付け加える。後継ハッシュとしては2018年末にSHA-256が選ばれた。候補にはSHA-512/256、SHA-256x16、K12、BLAKE2bp-256などがあった。
Git 3.0で変わるのは厳密には一点だけだ。git init で新しく作るリポジトリのデフォルトobject formatがsha256になる。BreakingChangesドキュメントは「現時点でsha1 object formatを非推奨にする計画はない」と明記し、この変更の重要な前提条件として「人気のあるGitライブラリ、アプリケーション、フォージ(forge)を含むエコシステムがsha256をサポートする準備が整うこと」を挙げている。3.0のリリース日はまだ決まっていない。
Chaconが指摘する摩擦は、この前提条件が現実にどれほど重いかという話である。彼は、今でも次のコマンドでSHA-256リポジトリを実際に作れることを示す。
git init --object-format=sha256 /tmp/twofiddy
そして彼が挙げる問題は次のとおりだ。
第一に、フォージ側の選択である。記事によれば、こうして作ったリポジトリは現時点ではGitHubにpushできない。彼は3.0のリリースまでにはほぼ確実に解決されるだろうし、むしろそれが3.0を遅らせている主因だろうと推測する。問題は解決された後だ。サーバー側でリポジトリを作る際にsha1かsha256かを合わせる必要があり、二つの形式は混在できない。彼は次のようなエラーを大量に目にすることになると予告する。
fatal: the receiving end does not support this repository's hash algorithm
第二に、サブモジュールである。同じ形式のプロジェクト同士でしかサブモジュールとして束ねられないため、ライブラリは既存プロジェクトと新規プロジェクトの双方で使われるには二系統を維持しなければならない、という主張だ。
第三に、既存リポジトリを変換する際のコストである。すべてのオブジェクトを再ハッシュする必要があり、既存の署名は壊れ、SlackやメールやIssueトラッカーに残ったSHA-1ハッシュのリンクはすべて切れる。ハッシュを40文字と仮定している社内ツールも壊れる。
第四に、ライブラリのエコシステムだ。Chaconは、Git本体がリエントラントでなく、GPLで縛られ、リンクできないライブラリという形で開発されてきたため、エコシステムの大半はゼロから再実装したライブラリを使っており、それらのSHA-256サポートは皆無か部分的だと書く。gitバイナリを直接fork-execしないスクリプトやツールは、ある程度壊れるだろうという話である。
彼はさらに、GoogleのEmily ShafferがGoogleのこの問題への備えについて講演し、Googleが社内全体の設定で新規プロジェクトを引き続きSHA-1で作らせる可能性に触れたと伝えている。この講演内容は記事が伝えるものであり、筆者は講演そのものを確認できていない。
衝突攻撃は本当に理論上の話にすぎないのか
Chaconの論証の核心はコスト側ではなく便益側にある。彼はSHA-256移行で得られるセキュリティ上の利益はほとんどないと見る。その論理を三段階に整理するとこうなる。
第一に、衝突攻撃と第二原像攻撃(second-preimage)を区別すべきだという。既に存在する他人のファイルと同じハッシュを持つ悪意あるファイルを作るのが第二原像攻撃で、これはMD5ですら事実上不可能だと彼は書く。地球上の約30億台のGPUをすべてRTX 5090に置き換えてMD5だけを計算させても、特定の原像を見つけるのに期待値で約160億年かかるという計算を示す。残るのは衝突攻撃、すなわち攻撃者が最初から無害なファイルと悪意あるファイルを一対で作り、無害な方を先に受け入れさせてから差し替えるシナリオだ。
第二に、ハッシュはSCMの世界における信頼のメカニズムではない。彼はLinus Torvaldsが2005年に残した「SHA-1をセキュリティだと考えるな。本当のセキュリティは配布(distribution)にある」という言葉を引用する。我々がrust-langのリポジトリを信頼するのは最新コミットの署名のおかげではなく、GitHubの認証がきちんと機能していると信じているからだ、というわけである。
第三に、実際の攻撃はハッシュ衝突ではなくソーシャルエンジニアリングで起きる。数十万ドル分のGPUを回すより、疲れ切ったOSSメンテナに4万ドルを払ってプロジェクトを譲り受けるほうがはるかに安く確実だという。Hacker NewsユーザーのJaumeGarは、xzバックドアがまさにその実例だと指摘した(直接の引用ではなく、コメントの要旨の要約)。ハッシュ衝突ではなく、メンテナへの信頼が崩れた事件だったということだ。
この論証には強い反論が付いた。ユーザーkpcyrdは記事に誤りが多いと反駁した(直接の引用ではなく、コメントの要旨の要約)。SHAtteredは理論ではなく実用的な概念実証であり、Gitが影響を受けなかったのは攻撃者がGitのblobプレフィックスに合わせて総当たりをしなかったからにすぎない。二つのリポジトリがフルのコミットハッシュで検証された同じコミット上にありながらチェックアウト内容が異なり得るなら、衝突攻撃だけでコードの密輸が成立する。そしてLinusの発言はむしろ「コンテンツアドレスを信頼の手段に使うな」という文脈だった、というのである。
ユーザーStrilancの反論はより実務的だ(直接の引用ではなく、コメントの要旨の要約)。「見知らぬ人が作ったファイルをどうやって取得させるのか」という問いは、GitHubの時代には成り立たない。知らない人がPRを送り、レビュアーがそれを git fetch してローカルでテストするのは日常である。「取ってきたら終わり」というのはセキュリティ境界として受け入れられない、と。彼はWebKitがSHA-1衝突のPDFをテスト用にSVNリポジトリに入れたところリポジトリが壊れた件を例に挙げ、ソフトウェアはしばしば衝突が起きない前提で作られているため、衝突そのものが予期せぬバグを生むと指摘した。ユーザーcomputerfriendは、我々はコミットハッシュにピン留めしてそれが不変の内容を指すと期待しており、コミットやタグの署名もそのハッシュの上にかかっているのだから、ハッシュの性質はそのままGitのセキュリティ上の性質だと書いた(直接の引用ではなく、コメントの要旨の要約)。
ここでGit公式ドキュメントの記述が興味深い。hash-function-transitionドキュメントは、ハッシュによるアドレッシングの利点として整合性チェックと高速なルックアップを挙げたうえで、暗号学的に安全なハッシュがもたらす追加の利点として「オブジェクト名に署名でき、第三者はそのハッシュが署名されたオブジェクトとそれが参照するすべてのオブジェクトを指すと信頼できる」ことを明記している。つまりGitプロジェクト自身は、ハッシュを信頼の連鎖の一部として設計ドキュメントに書き込んでいる。Chaconの言う「ハッシュで信頼するな」という原則と、Gitが実際に提供する署名機能のセマンティクスは食い違っている。署名付きタグがSHA-1ハッシュの上に署名している限り、署名の強度はSHA-1の衝突耐性に縛られる。
Chaconの代替案はまさにこの食い違いを狙う。彼は「独立ツリーハッシュヘッダ」という案を出す。コミットやタグに署名する際、ツリー全体の内容をSHA-256(あるいはBLAKE3など)で別途ハッシュして新しいヘッダとして入れ、そのヘッダまで含めて署名しようというものだ。SHA-1はコンテンツ取得用のキーとして残し、信頼の検証は別のハッシュが担う。これは新しいアイデアではなく、Colin Waltersのgit-evtagが2015年からほぼ同じことをしてきたと彼は明かす。彼の概念実証は、サブモジュールを再帰的に含めたChromium(作業ツリー35GB、210万ファイル)をM5 Macで5秒、Linuxツリー(1.5GB)を257ミリ秒、Gitプロジェクトを17ミリ秒でチェックサムしたという。数値は筆者自身の計測である。
この提案には明確な限界もある。Chacon自身が認めるとおり、すべてのコミットではなく、ヘッダを含み署名されたオブジェクトしか信頼できない。署名の信頼が履歴全体に伝播しないのだ。また通常のミラーやクローンの間で、同じSHA-1名が異なる内容を指す可能性そのものは残る。ユーザーSmasherEpileptiは、この提案が記事で最も興味深い部分なのに最も議論されていないと惜しみつつ、ミラーでの衝突問題はなお残ると指摘した(直接の引用ではなく、コメントの要旨の要約)。
デフォルトというレバー
議論で最も生産的だったのは、「Chaconが描いた苦痛のうち、何が設計上不可避で、何がまだ実装されていないだけなのか」を切り分ける部分だった。
ユーザーGrantMoyerとgorgoilerは、Gitのhash-function-transitionドキュメントを根拠に反論した(直接の引用ではなく、コメントの要旨の要約)。ドキュメントの目標(Goals)には、SHA-256への移行は「ローカルリポジトリ一つずつ、他の当事者の対応を必要とせずに」行えること、SHA-256リポジトリがSHA-1サーバーとpush/fetchで通信できること、ユーザーがSHA-1とSHA-256の識別子を相互に使えることが記されている。SHA-256リポジトリは二つの名前の間の双方向マッピングをローカルに保持し、署名もSHA-1側、SHA-256側、その両方で行えるよう設計されている。設計どおりに実装されれば、古いリンクや署名の多くは生き残る。
しかし同じドキュメントは限界もはっきり書いている。非目標(Non-Goals)には「GitプロトコルへのSHA-256サポート追加」「一つのリポジトリ内で複数のハッシュ関数のオブジェクトを混在させること」「既存のSHA-1署名オブジェクトのセキュリティを透過的に改善すること」が含まれる。注意事項(Caveats)には、この設計はすべての参照オブジェクトをローカルの変換テーブルに要求するため、シャロークローン(shallow clone)や未取得のサブモジュールをサポートしないとあり、GitプロトコルがSHA-256をサポートするまでは公開サーバーでSHA-256リポジトリを使うことを「強く推奨しない」とまで書かれている。サブモジュールのポインタを変換するには、変換済みのサブモジュールリポジトリが既に手元にある必要がある。ユーザーpavonとthunderforkが「サブモジュールの互換性とツールの幅」こそ最も難しい部分だと強調したのは、このドキュメントの内容と符合する。この設計ドキュメントの各段階が現在どこまで実装されているかは、筆者は今回確認できていない。
となると問いは、「デフォルトを変えること」が正しいレバーなのかに絞られる。双方の論理にはどちらも一理ある。
デフォルト切り替えを支持する側の論理は、ユーザーltbarcly3が最も簡潔にまとめた(直接の引用ではなく、コメントの要旨の要約)。デフォルトをそのままにすれば誰もSHA-256に移らず、GitHubも実装する理由がない。そして10年後にSHA-1が本当に破られたら、全員が同じ日に一斉に移行しなければならなくなる。デフォルトはエコシステムに前もって準備させる強制装置だ、というわけである。ユーザーnicoburnsがSHA-256は「もう一つのIPv6」になる兆しを見せていると懸念したこと(直接の引用ではなく、コメントの要旨の要約)は、逆説的に、強制装置がないと何が起きるかを示す比喩でもある。
反対側の論理は、Chaconがコメントで自ら述べている(直接の引用ではなく、コメントの要旨の要約)。SHA-256をオプションとして置くのは構わない。問題はデフォルトだ。デフォルトが変われば、その意味を知らない人々が他のリポジトリ、ツール、ライブラリ、サーバーと互換性のないリポジトリを大量に作ることになる。気にしたくない人に理解しづらい問題を押し付けるのがデフォルト変更だ、という主張である。
その間に置かれる中間案も出た。ユーザーkazinatorは、git init で形式を明示しなければ診断メッセージとともに失敗させようと提案し、kccqzyとplorkyeranは、フォージがリポジトリ作成時ではなく最初のpush時に形式を検出すれば、Chaconの描いたUXの問題の大半は消えると反論した(いずれも直接の引用ではなく、コメントの要旨の要約)。ユーザーgandreaniは、FossilがSHAttered公表の6日後である2017年3月1日にSHA3-256を代替として追加し、現在では新規リポジトリとチェックインのデフォルトがSHA3-256でありながら、一つのリポジトリ内にSHA1とSHA3-256のハッシュが共存できるため、リポジトリの再構築もリンク切れも起きなかったというFossilのドキュメントを引用した。Fossilのドキュメントはさらに、Gitのリポジトリは9年経っても一度に一つのハッシュアルゴリズムしかサポートしない、と付け加えている。
Fossilの事例が示すのは、コストの大きさがハッシュアルゴリズムからではなく「混在を許すかどうか」という設計判断から生じるという点だ。GitはSHA-1名とSHA-256名の一対一対応(双方向変換)を維持する代償として、リポジトリ単位の分離を選んだ。その選択がサブモジュールやシャロークローンの制約につながっている。ユーザーamlutoが提案した方式、つまりある時点までに存在したSHA-1オブジェクトの一覧をSHA-256で封印し、それ以降は新しいSHA-1オブジェクトを受け付けないという方式も、同じ問題意識から出てきたものだ。
規制の側面も見落とせない。BreakingChangesドキュメントがNISTとFIPS 140-2を根拠に挙げたことについて、ユーザー0x00clはこの変更がセキュリティよりも認証要件の政治に近いと見た(直接の引用ではなく、コメントの要旨の要約)。Chaconは記事の末尾で、NISTの2030年のSHA-1期限は「暗号学的保護を適用するために」SHA-1を使うことに関するものであって、スタックのどこかにSHA-1が存在すること自体を禁じるものではなく、すべての署名がSHA-256のコンテンツハッシュを覆うならSHA-1は何も保護しない単なるキーになると主張する。この解釈が実際の認証審査で受け入れられるかは別問題であり、筆者はNISTの原文でこれを検証していない。
結論
この論争を「SHA-1は安全か」の問題として読むと結論は出ない。双方ともSHA-1が弱いことには同意しており、ソーシャルエンジニアリングがハッシュ衝突より一般的な攻撃経路だという点にも大きな異論はない。分かれるのは二点である。一つはGitのハッシュが信頼の連鎖の一部なのかどうか、もう一つはエコシステムを動かす手段としてデフォルトを使うのかどうかだ。
一つ目の問いについては、Git公式ドキュメント自身が答えを書いている。署名がハッシュの上にかかっている限り、ハッシュは信頼の一部である。Chaconの独立ツリーハッシュヘッダ案は、この点を否定するというより、信頼をハッシュアルゴリズムの交換から切り離そうという提案として読むほうが正確だ。その方向性は、将来またハッシュを替えなければならないときのコストを下げるという意味で、真剣に検討する価値がある。ただしミラー間の衝突問題を残すという限界も合わせて検討すべきである。
二つ目の問いは、結局は順序の問題だ。BreakingChangesドキュメントは自ら「エコシステムの準備が整うこと」をこの変更の重要な前提として書いた。であれば、デフォルトの切り替えはエコシステムに準備させるレバーではなく、準備が終わったことを宣言する最後のステップであるべきだ。サブモジュール、シャロークローン、プロトコルサポートが設計ドキュメントの非目標や注意事項として残っているうちにデフォルトを先にひっくり返せば、その空白を埋めるコストは意味を知らないユーザーに回ってくる。
あなたのチームで、コミットハッシュを40文字の文字列と仮定しているスクリプト、サブモジュールの依存関係、コミットハッシュに固定されたSBOMや来歴証明のデータが何か所にあるか、今数えられるだろうか。その数字が、Git 3.0があなたに請求する実際のコストである。
出典: