【ITニュース解説】Your PDF says AES-256. The key had 9 bits of entropy
2026年10月29日に「Dev.to」が公開したITニュース「Your PDF says AES-256. The key had 9 bits of entropy」について初心者にもわかりやすく解説しています。
ITニュース概要
PDFはAES-256暗号と表示されても、一部の実装でキー生成に脆弱性が発覚した。MuPDFやFoxitなどは、時刻や不適切な乱数からキーを作り、パスワードなしで数秒で解読される。強い暗号でもキーが弱ければ意味がなく、PDFパスワードは機密保護に不十分だ。
ITニュース解説
PDFファイルにパスワードをかけると、その文書のプロパティには「AES-256」という強力な暗号化方式が使われていると表示されることが多い。このAES-256は、現代において非常に安全性が高いとされている暗号化アルゴリズムの一つであり、多くの人がこれを見れば安心して、自分のPDFがしっかりと保護されていると考えるだろう。しかし、今回明らかになったのは、その「鍵」の生成方法に大きな問題があり、AES-256が使われているにもかかわらず、ほとんどのPDFが容易に解読できてしまうという衝撃的な事実だった。
PDFの暗号化がどのように機能するはずだったのかをまず理解しよう。あなたがPDFにパスワードを設定したとき、そのパスワードが直接文書全体を暗号化するわけではない。代わりに、PDFソフトウェアはまず32バイトのランダムな「ファイルキー」というものを生成する。このファイルキーこそが、AES-256を使ってPDFの内容を暗号化する主役となる。そして、あなたが入力したパスワードは、ソルト(暗号化の際に加えられるランダムなデータ)とハッシュ化(一方向の変換)を経て、このファイルキーを暗号化するための「別の鍵」として使われる。つまり、文書のセキュリティは、最終的にはその「32バイトのファイルキーがどれだけ予測しにくいか」にかかっている。もし攻撃者がこのファイルキーを推測できてしまえば、パスワードや複雑なハッシュ化の仕組みは全く意味をなさなくなる。まさにこの「ファイルキーの予測しにくさ」の部分で、いくつかのPDF実装が致命的な失敗を犯していた。
まずMuPDFというPDFビューアーの事例だ。バージョン1.28.5以前のMuPDFは、ファイルキーを生成する際にdrand48という種類の乱数生成器を使っていた。これは、シミュレーションやゲームで使われるような、特定のパターンで動く予測しやすい乱数生成器だ。さらに悪いことに、この生成器の初期値(シード)には、パソコンの現在の時刻を秒単位で取得した「time(NULL)」という値が使われていた。しかも、その時刻情報は32ビットに切り詰められており、乱数生成器の内部状態のうち、ごく一部のビットしか出力に使われなかった。致命的だったのは、この生成器が利用する25ビットの重要な状態のうち、16ビットが常に固定の「0x330e」という値だったことだ。結果として、ファイルキーのランダム性は、わずか9ビット分の「時刻のずれ」によってしか生み出されていなかった。9ビットの情報というのは、たった2の9乗、つまり512通りのパターンしかないことを意味する。攻撃者はこの512通りのキー候補をすべて生成し、一つずつ試していくだけで、わずか11ミリ秒ほどで正しいファイルキーを見つけ出すことができた。さらに、MuPDFはPDF文書の/IDという部分に、ファイルキーと同じ乱数生成器から作られた情報を平文で保存しており、これが攻撃者にとって鍵が正しいかを確認する「ヒント」として機能してしまった。一度ファイルキーが特定されれば、パスワードが何であろうと文書は簡単に開かれてしまう。
次にFoxit PDF Readerの事例だ。Foxit PDF Reader 2026.1.3.36551では、ファイルキーはSHA-256という強力なハッシュ関数を使って生成されていた。しかし、そのハッシュ関数の入力値が問題だった。入力値は「現在の時刻(秒単位)」、「32バイトの未初期化メモリ(実際にはゼロになることが多い)」、そして「”there”という固定の文字列」を結合したものだった。ここでも、攻撃者にとって予測困難な情報(エントロピー)は、たった32ビットの「時刻」のみだった。未初期化メモリはゼロであることが多く、固定文字列は常に同じだ。結局、ファイルキーのランダム性は、時刻の範囲によって決まってしまう。Foxitの実装では、PDF内部の/Permsという部分に、既知の平文(暗号化されていない内容)が含まれていることが判明し、攻撃者はこれを利用して、候補となる鍵が正しいかどうかを非常に高速に検証できた。V12というセキュリティ研究チームが検証したところ、最新のCPUを使えば、1970年1月1日のUnixエポックから現在までのすべてのタイムスタンプ候補をわずか4.85秒で総当たりし、正しいファイルキーを見つけ出すことが可能だった。この問題は、FoxitがPDFiumというPDFレンダリングエンジンの初期コードから継承したもので、10年以上にわたって多くのPDF関連ソフトウェアに影響を与えていたと考えられる。
OpenPDFやiTextといったライブラリも同様の問題を抱えていた。これらのライブラリは、ファイルキーを生成する際に「現在の時刻(ミリ秒単位)」と「JVM(Java仮想マシン)の空きメモリ量」という、予測しやすい情報を組み合わせて、ARC4という暗号ストリームのキーとして利用していた。そして、ファイルキーだけでなく、PDF内に平文で保存される「ソルト」も同じ暗号ストリームから生成されるため、攻撃者はソルトを手がかりに、時刻とメモリ量の組み合わせを推測し、最終的にファイルキーを特定することができた。これも「見かけ上の複雑さ」と「実際の脆弱性」のギャップを示す事例だ。
これらの事例が共通して示しているのは、暗号化アルゴリズム自体は強力でも、その「鍵」を生成する部分の設計が甘ければ、セキュリティは簡単に破られてしまうということだ。根本的な原因は、「ランダムだと誤解されやすいが、実際には予測可能な情報」を暗号鍵の生成に使ってしまったことにある。
random()は秘密ではない:drand48のような乱数生成器は、シミュレーションやゲームのような用途には適しているが、暗号鍵のような秘密の情報を生成するには全く不向きだ。これらは数学的な式に基づいており、初期値(シード)が分かれば、次にどんな値が生成されるか予測できてしまう。- 時刻はエントロピーではない: 時刻は連続した値であり、文書の作成日時などのメタデータから、ある程度の範囲を推測されやすい。攻撃者は「大体このくらいの時間に作成されただろう」という推測から、総当たり攻撃の範囲を狭めることができる。
- 未初期化メモリはエントロピーではない: 未初期化のメモリ領域には何が入っているかわからない、という点で一見ランダムに見えるかもしれないが、実際にはゼロで埋められていることが多く、たとえ何か値が入っていたとしても、それはセキュリティのために頼れるようなランダムな値ではない。
本来、安全な暗号鍵を生成するためには、getrandomや/dev/urandom(Linux)、CryptGenRandom(Windows)、SecureRandom(Java)といった、OSやプラットフォームが提供する「真に予測不能な乱数」を生成する機能を使うべきだった。これらの機能は、システムの物理的なノイズ(マウスの動き、キーボードの入力タイミング、ハードディスクの動作音など)を元にして、予測困難なランダムなデータを生成する。
この問題は、実務において非常に重要な意味を持つ。PDFにパスワードをかけているからといって、その文書が本当に機密性を保てるとは限らない。PDFのパスワードは、「正直な読み手に対して、この文書を見る許可があるかどうかを示すヒント」程度に考えるべきだ。本当に秘密にしておきたい文書、例えば契約書や医療記録、重要な手紙などについては、PDFファイル自体をageやGPG、あるいはOSの暗号化機能(暗号化されたZIPファイルなど)といった、別の専門的なツールで「ファイル全体を暗号化」してから送る必要がある。
また、もしあなたがソフトウェア開発に携わっているのであれば、自分の書いたコードや、自分が利用しているライブラリのコードを徹底的に見直すべきだ。暗号鍵、認証トークン、初期化ベクトル(IV)、ソルトなどのセキュリティに関わる重要な値が、「時刻」「プロセスID」「メモリ統計」といった予測可能な情報や、「非暗号学的な乱数生成器」から生成されていないかを必ず確認しよう。MuPDFの事例のように、一度脆弱な鍵で暗号化されて公開されてしまった文書は、後からソフトウェアを修正しても、以前の脆弱な鍵で暗号化された状態のままであり、攻撃者によって簡単に解読されてしまうため、過去の文書を守ることはできない。
この一連の事例が示しているのは、複雑な暗号技術それ自体が破られたわけではなく、その土台となる「鍵の生成」という、一見地味だが極めて重要な部分が甘かったという点だ。多くの開発者が「AES-256」という強力なラベルに安心してしまい、その裏側にある鍵のランダム性、つまり「玄関マットの下に置かれた鍵」と同じ状態になってしまっていたことを、このニュースは教えている。世界最強の暗号化方式も、その鍵が簡単に推測されてしまえば、何の役にも立たないのだ。