
2025/12/02 7:44
Constructing the Word's First JPEG XL MD5 Hash Quine
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
記事が主に伝えたいこと(メインメッセージ)
著者はJPEG XL用の「Hash Quine」を構築しています。これは、自身のMD5ハッシュを表示しつつ有効なファイルであり続ける画像です。同一プレフィックスMD5衝突ブロックのペアを埋め込み、任意のペアを入れ替えることで、可視化された32桁の16進数ハッシュを変更でき、元のMD5値は変わりません。
証拠/根拠(なぜそう言われているか)
JPEG XL のロスレスモジュラーモードと、すべてのバイトを有効トークンにマップするカスタムハフマン木により、任意のビットフリップが可能です。著者は128個の衝突ブロックペアを作成し、各ペアで表示されるハッシュの1桁(16進数)をトグルできます。この手法では Marc Stevens の
fastcoll を用いて衝突を生成し、その後 90k ノード以上にわたる予測木でフリップされたビットを探索。4つをカスタム加算トリックで1桁の16進数に組み合わせ、Orbitron スタイルのビットマップとして描画します。
関連ケース/背景
PDF、ZIP、GIF、PNG などには既にハッシュクインが存在していました。GitHub の
corkami/collisions リポジトリにはそれら形式の例がホストされています。本作業はその概念を JPEG XL に拡張したものです。
今後起こり得ること(将来の展開)
現在のファイルサイズは約 2 MB と大きいのは、圧縮されていないハフマン木が原因です。将来的には色チャネルを制限し予測木を最適化することでサイズを削減できる可能性があります。また、Windows コーデックなど広範な JPEG XL サポートが必要で、より多くの採用へとつながります。
影響について
ファイル形式特有の挙動がデータを隠蔽または変更しつつ暗号学的チェックサムを保てることを示すため、セキュリティ上の懸念を喚起します。同時に、開発者がさまざまな形式でハッシュクインを試験できる創造的ツールとしても機能します。
主要ポイントは全て反映されており、新たな推論は導入されていません。
本文
作者:Amy (itszn) – Burnett
「クイン(quine)」という言葉を聞いたことがあるでしょう… それは自分自身のソースコードを出力するプログラムのことです。
Python の例で示すと:
$ cat quine.py s='s=%r;print(s%%s)';print(s%s) $ python3 quine.py s='s=%r;print(s%%s)';print(s%s)
自己参照的なフラクタル詩のように… プログラムの出力がそのプログラム自身です。
ハッシュクイン(Hash Quines)
自分のソースコードを印字する代わりに、ハッシュクイン は自分自身のハッシュ ― すなわちそのアイデンティティを暗号的に示すトレース ― を表示します。
この記事の終わりまでに、MD5 ハッシュを自ら表示する画像を作る方法を学びます:
$ md5sum shark_hashquine.jxl c0dec0007b5246f7428936d9bed2f446 shark_hashquine.jxl
注:ブラウザが JPEG XL をサポートしていません!
ハッシュを確認するには、Safari のような対応ブラウザで開くかファイルをダウンロードしてください。下に示す PNG 版は同じ MD5 を持ちません。.jxl
ハッシュとは何か?
ハッシュはファイル全体を一方向関数に通した結果得られる、見た目には無意味な文字列です。
ソースコードがプログラムの「魂」を表すように、ハッシュもまたファイルそのものを代表します。
$ md5sum ./my-cool-file.pdf 6cef0cf6194efa36cb5be483ce87bd3b my-cool-file.pdf
クインが自分のハッシュを印字することで、Quine Cinematic Universe(QCU)は他のファイル形式にも拡張されます。新しいメディアを別々の計算パラダイムで学ぶことができます。
それはどうやって可能なのか?
「あるファイルが自分自身のハッシュを表示する」という固定点を見つけるのは不可能に思えます――
ハッシュを変えると、再びハッシュも変わってしまうからです。
MD5 の弱点
MD5 は壊れていると言われており、衝突(collision)を簡単に見つけられます。
「同一接頭辞衝突」攻撃では、任意の前置き文字列に 128 バイトの衝突ブロックを付加でき、同じ末尾を追加しても衝突が保たれます。
ここで fastcoll(Marc Stevens 作)を使い、これらのブロックを生成します。
任意のプレフィックスとサフィックスを与えることで、画像データに衝突ブロックを埋め込むことが可能です。
JPEG XL を選んだ理由
JPEG XL はモダンで多機能なフォーマットで、積極的に開発されています:
- macOS / Safari
- Windows(公式コーデック)
- PDF 標準仕様への組み込み
- Chromium 側の関心(Rust 実装も予定)
私は JPEG XL の ロスレスモジュラー モード が「予測木(Prediction Tree)」と残差ストリームを利用している点に惹かれました。
予測木はピクセル座標や隣接ピクセルなどで分岐比較を行う小さな VM のようなものです。これにより、画像の描画プロセスそのものにロジックを埋め込むことができます。
残差ストリームの準備
JPEG XL は残差を次の手順で圧縮します:
- トークナイズ
- ANS/Huffman エントロピー符号化
- LZ77 圧縮
MD5 衝突ブロックはランダムなバイト列なので、通常はデコードできません。
そこで
libjxl を改造し、すべての 256 バイト値に対して 均等ヒストグラム(force_full_alphabet)を強制します。これで 8 ビット全パターンが有効トークンとなり、残差ストリームはデコーダーから見れば生バイト列になります。
衝突ブロックの埋め込み
生バイトストリームを用意したら、任意の衝突ブロックペアを画像ファイルに差し替えても MD5 は変わりません。
各差し替えは特定ピクセル(多くはアルファチャンネル)をフリップさせるだけで、ビットをエンコードできます。
ビットフリップを十六進表示に変換
1. フリップ検出(「プローブ」)
固定しきい値(例:64)を選び、衝突ブロックがピクセルを下から上へとフリップする箇所を検索します。
予測木の条件分岐で
2**N を出力させることでビット位置 N を得ます。
2. ビットの蓄積
予測木は限られた算術しか扱えないため、
Gradient 演算子を利用したトリックを使います:
acc + 32 - (32 - B) == acc + B
ここで
N = 32、W = アキュムレータ、NW = 32 - B とすればビットを正しく加算できます。
3. ビットの配列
北(
N)と西(W)演算子で、上左隅から下へ、右へ向かって蓄積値を各桁位置に伝搬させます。
4. デジット描画
Orbitron フォントの 15×15 ビットマップを描き、すべてのピクセル値(0–15)を対応する文字像へとマッピングする大規模な条件付き予測木を構築します。
ノード数は約 90 k ですが正しく動作します。
最終組み立て
- MD5 衝突ブロックの 128 ペアを生成
- 各ペアを JPEG XL ファイル上の指定位置に配置
- 一度画像を描画し、必要な 128 ビットパターン(例:任意のハッシュ)をエンコードするためにブロックを順次差し替え
- 任意で末尾バイトをブルートフォースしてリートスピークやその他の vanity を埋め込む
結果
- ファイルサイズ:約 2 MB(残差が圧縮されていないため)※選択的に圧縮すれば削減可
- ハッシュ例:
$ md5sum with_* 6812e709a47c620a679850629e66f42c with_41414141.jxl 6812e709a47c620a679850629e66f42c with_deadbeef.jxl 6812e709a47c620a679850629e66f42c with_md5.jxl
- 最終画像:
$ md5sum shark_hashquine.jxl c0dec0007b5246f7428936d9bed2f446 shark_hashquine.jxl
最後に
このプロジェクトは JPEG XL の機能を深く理解する良い機会となり、予測デコーダが小さなプログラミング環境として働くことを示しました。
今後の改善点としては:
- 選択的 Huffman でファイルサイズを削減
- よりスマートに生成した 90 k ノードの予測木を最適化(または JPEG XL の patch 機能を活用)
ご覧いただきありがとうございます!
インスピレーションが湧いたら、他のファイル形式でハッシュクインを作ってみてください。各フォーマットには独自の計算 playground が待っています。