Skip to content
ブログに戻る
セキュリティ

MD5をSHA-256に変換できる?不可能な理由と目的別の正しい対処法

MD5ハッシュをSHA-256に変換することはできません。ハッシュは一方向です。変換ツールの実態と、目的別の正しい対処法を解説。無料のオンラインツールも。

11 分で読める

MD5をSHA-256に変換できる?不可能な理由と目的別の正しい対処法

それをやると謳うページは山ほどあり、なかには本当に動くツールに見えるものもある。だが、そこで宣伝されている操作は存在しない。なぜ存在しないのかが分かれば、次に何をすべきかも見えてくる。

MD5 を SHA-256 に変換することはできない(逆方向も同じ)

MD5 ダイジェストは 128 ビットの出力で、それを生み出した入力を一切含んでいない。一方 SHA-256 は、何かを計算するのにその入力そのものを必要とする。だから MD5 ハッシュを SHA-256 に変換することはできない。元データがなければ途中の一手が永久に欠けたままで、どんなツールもライブラリもその穴を埋めてはくれない。

同じ入力を 3 つのアルゴリズムにかけると、こうなる。

アルゴリズムhello のダイジェスト16 進の文字数ビット長
MD55d41402abc4b2a76b9719d911017c59232128
SHA-1aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d40160
SHA-2562cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982464256

3 つのダイジェストを文字列として並べてみるといい。共通の接頭辞も部分文字列もなく、値どうしに算術的な関係もない。3 つを結びつけているのは hello という単語だけで、しかもそのどれ一つとして hello を内側に持っていない。MD5 ハッシュ生成ツールSHA-256 生成ツールに同じ文字列を入れれば、打つそばから両者が互いに無関係に変化していくのが見える。

方向を逆にしても事情は変わらない。SHA-256 から MD5 へ、というのもまったく同じ理由で失敗する。どちらの関数も意図的に情報を捨てているので、片方のダイジェストを、もう片方が必要とする入力まで巻き戻せない。

ハッシュ理論への好奇心からこのクエリを打ち込む人はいない。では、本当にやりたかったことは何なのか。実務ではだいたい次の 4 つのどれかで、それぞれにちゃんとした答えがある。

  1. ダウンロードの検証、またはチェックサムの照合
  2. パスワードデータベースを MD5 から移行する
  3. ハッシュの元になった入力を復元する
  4. 同じデータを別のアルゴリズムでハッシュ化する

4 つはあとで順に扱う。ただしその前に、変換を謳うページが実際には何をしているのかを知っておくと、この種のページの出力を信じてしまう事故を防げる。

「MD5 → SHA-256 変換ツール」ページが実際にやっていること

可能性は 3 つしかなく、そのどれも変換ではない。

いちばん多いのは、元のテキストを入力させるページだ。文字列を貼り付けると両方のダイジェストが並び、宣伝文句がそれを変換と呼ぶ。実体は、ラベルを付け替えただけのハッシュ生成ツールだ。入力を 2 回ハッシュ化しているだけで、変換はどこにも起きていない。

一部のページは、ダイジェストを検索する。ワードリストや流出した認証情報のダンプから集めた「入力とダイジェストの対応表」をページ側が持っている。その表に入力が載っている MD5 を貼り付ければ、ページは入力を引き当て、それを SHA-256 でハッシュ化して結果を見せる。password123456、その他の短い文字列のロングテールに対しては機能する。それ以外では失敗する。この種のページが説明もないまま空欄を返してくるのはそのためだ。

そして、驚くほど多くのページは何もしていない。単に入力をそのまま再表示するか、貼り付けられた 16 進文字列の SHA-256 を出しておきながら、それが元データの SHA-256 であるかのように匂わせる。この 2 つは別の値だ。どれだけ別物かは後の二重ハッシュの節で正確に示す。

見分け方は簡単だ。32 文字の 16 進文字列だけを渡し、そのページに元の入力を知る術があり得るかを考えてみればいい。それでも 64 文字の結果を返し、それを変換と称するなら、そのページはルックアップテーブルを引いているか、嘘をついているかのどちらかだ。元データを一度も尋ねてこないハッシュ変換ツールには、偶然以外に正解へたどり着く手段がない。

この言い回しが生き残っているのは、隣接する「本当に機能する操作」とよく似て聞こえるからだ。

ハッシュ化・エンコード・暗号化:可逆なのは 3 つのうち 2 つだけ

開発者はこの 3 つを同じ週に目にして、頭の同じ引き出しにしまってしまう。しかし挙動はまったく違う。

操作可逆か必要なもの
エンコードBase64、16 進、URL エンコード常に可逆何もいらない
暗号化AES、RSA可逆
ハッシュ化MD5、SHA-1、SHA-256決して不可逆何をしても無理

間違った直感を育ててしまう犯人が Base64 だ。誰でも鍵も許可もなしに Base64 文字列をデコードして元のバイト列に戻せるし、Base64 を 16 進に変換するのもごく日常的で正当な操作である。どちらも同じバイト列の別の綴り方にすぎない。それが動くのを見たことがあれば、「MD5 を SHA-256 に」も同じ綴り直しに聞こえてしまう。

だが違う。エンコードは入力のビットを 1 つ残らず保持し、暗号化は鍵の向こうに保持し、ハッシュ化はそのほとんどを捨てる。MD5 ダイジェストは、食わせたのがパスワードだろうと 40 GB のディスクイメージだろうと 16 バイトだ。その 40 GB がどこか気の利いた場所へ移されたわけではない。消えたのである。暗号化との比較をもっと詳しく知りたければ MD5 ツールの FAQ にあるが、ここでは一行で足りる。鍵が存在しないのだから、復号すべきものがそもそもない。

「MD5 は破られた」は「MD5 は元に戻せる」という意味ではない

これは誠実な記事ですら取り違える。人は「MD5 は何年も前に破られた」から「ならもう誰かが元に戻せるはずだ」へ一直線に推論してしまう。

関係するのは 2 種類の攻撃だ。

  • 衝突攻撃は、同じダイジェストになる 2 つの異なる入力を見つける。攻撃者は両方の入力を自分で選べて、その中身が何であるかは気にしない。
  • **原像攻撃(preimage attack)**は、ダイジェストから出発して、それを生む入力を復元する。MD5 を SHA-256 に変換できるとしたらこちらの攻撃であり、SHA-256 に食わせるものを取り戻してくれるからだ。

MD5 の衝突耐性は 2004 年に破られ、今日では普通のハードウェアで数秒あれば衝突を構成できる。署名や証明書、その他攻撃者が影響を及ぼせるあらゆる用途に MD5 が不適格なのはこのためだ。

一方、MD5 の原像耐性は破られていない。知られている最良の原像攻撃は今なお理論上のもので、計算量は 2^123.4 である(Sasaki and Aoki, 2009)。総当たりよりわずかに速いだけで、実用的な意味では手の届く範囲にまるで入っていない。任意の MD5 ダイジェストを逆算できている者は、2004 年当時も今もいない。

つまり 2 つの事実は矛盾なく並び立つ。MD5 は破られている。そして MD5 は依然として不可逆である。変換しようとしているのではなく、新しいシステムでどちらのアルゴリズムを使うか選んでいる段階なら、MD5 vs SHA-256 の比較がその判断を正面から扱っている。

本当にやりたいこと:4 つのタスクとそれぞれの正解

実際にやろうとしていること本当の答え
ダウンロードの検証、チェックサムの照合必要なアルゴリズムでファイルから計算し直す
MD5 パスワードハッシュの移行bcrypt でラップし、ログイン時に昇格させる
元の入力の復元それは変換ではなく解読であり、エントロピー次第
同じデータを別アルゴリズムで再ハッシュ元のバイト列に戻る

タスク 1:チェックサムを照合したい、ダウンロードを検証したい

変換ではなく再計算する。ファイルはディスク上にあるのだから、ダイジェストが失った当のものを手元に持っている。必要なアルゴリズムをそのファイルに対して実行すればいい。

# GNU coreutils
md5sum    ubuntu-24.04-desktop-amd64.iso
sha256sum ubuntu-24.04-desktop-amd64.iso

# macOS
md5           ubuntu-24.04-desktop-amd64.iso
shasum -a 256 ubuntu-24.04-desktop-amd64.iso

よくぶつかるのは、配布元が MD5 しか掲載していないのに、自社のセキュリティポリシーは SHA-256 を要求している、というかみ合わなさだ。公開されている MD5 を変換できたとしても、それでは何も証明できない。あるダイジェストから導いた別のダイジェストは、そのファイルについての配布元の言明ではないからだ。必要なのは、配布元が署名した SHA-256 である。配布元が用意していない場合でも、SHA-256 生成ツールで自分の SHA-256 を計算しておけば、社内利用のための安定した指紋にはなる。値を記録しておけば、ミラー間で照合したり後の変更を検知したりはできる。ただし、配布元による裏書きの代わりにはならない。

タスク 2:パスワードデータベースを MD5 から移行したい

4 つの中でいちばん失うものが大きいタスクだが、定石は決まっている。古いハッシュをラップし、以後ログインのたびに各アカウントを裏側で移行するのだ。保存済みの MD5 ハッシュすべてを一度のバッチジョブで bcrypt にかけ、それ以降のログイン経路では、送られてきたパスワードをまず MD5 にかけてから bcrypt レコードと照合する。

ここで一つ注意がいる。古い記事の少なくない数がこれを逆に書いている。MD5 を SHA-256 でラップしてもパスワード保存は安全にならない。SHA-256 は高速ハッシュであり、MD5 とまったく同じ種類の問題を抱えていて、GPU はどちらも凄まじい速度で食い荒らす。データベース組み込みの SHA2() 関数を解決策として挙げるアドバイスは、問題設定からして間違っている。狙うべきは意図的に遅いハッシュ、すなわち bcrypt、scrypt、Argon2 のいずれかだ。保存するのは bcrypt(md5(password)) であって、sha256(md5(password)) では決してない。3 つのどれを選ぶかは bcrypt vs Argon2 vs scrypt の比較が扱っており、bcrypt 生成ツールを使えば、決め打ちする前にコストファクターを試せる。

ここからが、実際に移行を壊す部分だ。「パスワードの MD5」という表現は曖昧で、2 通りの読み方が別々の結果を生む。

const { createHash } = require('node:crypto');

const md5Hex = createHash('md5').update('hello').digest('hex');
const md5Raw = createHash('md5').update('hello').digest();   // the same value as 16 bytes

createHash('sha256').update(md5Hex).digest('hex');
// 4914e23374bb211e3dca0df7636fefffc7fedd94f1340ae81c7d6c07b7113e9b

createHash('sha256').update(md5Raw).digest('hex');
// 88e20f0abb88153e3f0a9683668ccb5b84ed771817dc448a2b73254ed02c8d8c

createHash('sha256').update('hello').digest('hex');
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

3 つの結果はすべて正しく、すべて異なる。32 文字の 16 進文字列をハッシュ化すればある値が出る。それが表す 16 バイトの生バイト列をハッシュ化すれば別の値が出る。元のパスワードをハッシュ化すればさらに別の値が出る。どの規約を採るかを決めて書き留め、移行スクリプトとログイン経路で同じものを使うこと。ここを間違えると全ユーザーが一斉にログインできなくなり、しかも症状はエンコードの選択ミスではなく bcrypt のバグのように見える。

bcrypt でラップするなら、16 進のほうが安全な規約だ。生のダイジェストバイト列にはゼロバイトが含まれることがあり、bcrypt の実装の中には入力を C 文字列として扱ってそこで読み取りを止めるものがいくつかある。16 進ならその問題は完全に回避できるし、32 文字なら bcrypt の入力長制限にも十分収まる。大文字小文字も正規化しておくこと。5D41402A...5d41402a... は同じダイジェストでも、bcrypt にとっては別の文字列だからだ。

const bcrypt = require('bcrypt');
const { createHash } = require('node:crypto');

const legacyDigest = (password) =>
  createHash('md5').update(password, 'utf8').digest('hex');   // lowercase hex

// Batch job, run once: wrap each stored MD5, then drop the old column.
async function wrapRow(row) {
  return bcrypt.hash(row.md5_hash.toLowerCase(), 12);
}

// Login path
async function verify(password, row) {
  if (row.scheme === 'bcrypt') {
    return bcrypt.compare(password, row.hash);
  }
  const ok = await bcrypt.compare(legacyDigest(password), row.hash);
  if (ok) {
    const upgraded = await bcrypt.hash(password, 12);
    await saveCredential(row.id, { scheme: 'bcrypt', hash: upgraded });  // your DB write
  }
  return ok;
}

アカウントは、持ち主がサインインするたびに素の bcrypt へ移っていく。取り残された分には期限を切り、期限を過ぎたらリセットを強制すればいい。

タスク 3:元の入力を復元しようとしている

せめて自分に対しては、はっきりそう言葉にしたほうがいい。ツールも見込みも変わってくるからだ。ダイジェストから入力を復元するのは変換ではなく解読(クラッキング)であり、成功するかどうかは、そのダイジェストを作ったアルゴリズムとは何の関係もない。入力次第である。

短くてありふれた、人間が選んだ文字列はすぐ落ちる。辞書に載っている単語、末尾に数字を付けた人名、漏洩コーパスに一度でも現れた文字列は、ルックアップか短時間の総当たりで落ちる。長いランダム文字列はまったく落ちない。そしてこの 2 つの間にある落差は、ハードウェアを買い増して埋まる類のものではない。

レインボーテーブルの話は雑に語られがちなので、条件を正確に書いておく。レインボーテーブルが入力を復元できるのは、その入力がソルトなしでハッシュ化されており、かつすでにテーブルに載っている場合に限られる。両方の条件が必要だ。ユーザーごとのソルトを加えれば、事前計算されたテーブルは無価値になる。攻撃者はソルトごとに別のテーブルを用意しなければならないからだ。128 ビットのランダムトークンをハッシュに食わせれば、それを収録したテーブルはこの世に存在しない。境界がどのあたりにあるかはパスワードエントロピー解説が数字で示している。

その入力が自分のパスワードで、どうしても再現できないのなら、これは復元ではなくリセットとして扱うべきだ。他人のものであるなら、正直な位置づけは上に書いたとおりである。

タスク 4:同じデータを別のアルゴリズムでハッシュ化したい(「md5 to sha1 converter」)

タスク 1 と同じ形を一般化しただけだ。要求に出てくるアルゴリズムの組み合わせが何であろうと関係ない。答えは常に、元のバイト列に戻ってもう一度ハッシュ化する、である。

sha1sum   payload.bin
sha256sum payload.bin

世に出回っている「md5 to sha1 converter」はどれも、上と同じ理由で、再ハッシュ器かルックアップテーブルのどちらかだ。元データが手に入らないなら、そのタスクに解はない。ツールを変えても結果は同じだ。目的のアルゴリズムを選ぶには SHA ファミリー比較を参照してほしい。SHA-1 生成ツールは、いまだにそれを要求するレガシーシステムのために置いてある。新規に作るものが使う理由はないが。

ハッシュ変換のルックアップが本当に機能する唯一のケース

データの中には、中央に集約され、主要なダイジェストすべてで同時にインデックス化されている種類のものがある。マルウェア検体が典型例だ。VirusTotal のようなサービスは検体そのものを保管しているので、同じバイト列から MD5、SHA-1、SHA-256 を計算し、3 つとも一つのレコードに紐づけて登録している。検体の MD5 をそこで検索すれば、その SHA-256 が返ってくる。

これは変換ではなくデータベースのジョインだ。機能するのは、誰かが元のファイルを持っていて、あらゆるアルゴリズムでハッシュ化しておいたからにすぎない。上で述べた再計算のステップを、第三者が前もって済ませてくれているだけである。条件は厳しい。対象がすでにインデックスに存在していること、そしてそれを構築した相手を信頼できること。脅威インテリジェンスの業務はこれに日常的に依存している。だがこれは任意の MD5 ダイジェストについては何も語らないし、自分のデータのハッシュには永久に役立たない。

仕組み自体は、怪しい変換ページが使っているものと同じだ。違うのは、マルウェアリポジトリはそれがルックアップであることを明示し、レコードそのものを見せてくれる点である。

自分で試してみる

これを腹に落とす一番速い方法は、同じ入力から出た 2 つのダイジェストが、互いに無関係なまま一緒に変わっていく様子を眺めることだ。

$ printf 'hello' | md5sum
5d41402abc4b2a76b9719d911017c592  -
$ printf 'hello' | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824  -

1 文字変えれば両方の出力が丸ごと変わる。しかもその変わり方は互いに何の関係もない。同じテキストを MD5 ハッシュ生成ツールSHA-256 生成ツールに並べて貼り付ければ、その場で確認できる。API リクエストへの署名など、本当に必要なのが鍵付きダイジェストだったなら HMAC ジェネレーターを使えばいい。3 つともブラウザ内だけで動く。

FAQ

MD5 を SHA-256 に変換できるのか?

できない。MD5 ダイジェストは元の入力を含んでおらず、SHA-256 は何かを出力するのにその入力を必要とする。変換すると謳うページはどれも、こちらが渡したテキストを再ハッシュしているか、事前計算されたペアのデータベースから値を引いているかのどちらかだ。

なぜこれほど多くのサイトが「MD5 → SHA-256 変換ツール」を提供しているのか?

人が実際に打ち込むクエリで上位表示できるからだ。たどり着いてみると、そのページは元のテキストを求めてくる(つまり変換ではなく再ハッシュだ)か、役に立つものを何も返さないかのどちらかである。誤解のほうがありふれているので、検索語だけが生き残り続けている。

SHA-256 を MD5 に戻すことは可能か?

不可能であり、方向は関係ない。どちらも一方向関数なので、元の入力なしに一方のダイジェストをもう一方へ変えられない。逆方向を提供するツールも、同じルックアップか再ハッシュの手品を回しているだけだ。

MD5 は破られているのだから、元に戻せるのでは?

戻せない。MD5 の衝突耐性が破られたのは 2004 年で、これは 2 つの入力に同じダイジェストを共有させられるという意味である。ダイジェストを逆算するのは原像攻撃であり、知られている最良のものでも依然として 2^123.4 回の演算を要する。署名用途としては破綻しているが、逆算はいまだにできない。

MD5 パスワードハッシュのデータベースを、より安全な方式へ移行するには?

MySQL で UPDATE users SET password = SHA2(password, 256) と一行で済ませるのは誤りで、ハッシュ化されるのは保存済みの MD5 ダイジェストであってパスワードではない。正しくは bcrypt(md5(password)) を保存し、同じラッパーを通して検証しつつ、次に成功したログインのタイミングで各レコードを昇格させる。MD5 を SHA-256 でラップしても助けにはならない。どちらも高速ハッシュであり、GPU はどちらも同じ容赦ない速度で処理してしまうからだ。

二重ハッシュの結果が期待と違ったのはなぜか?

MD5 の 16 進文字列をハッシュ化した場合と、それが表す生の 16 バイトをハッシュ化した場合とでは、SHA-256 の出力が異なる。どちらも正しく、単に入力が違うだけだ。どちらか一方の規約を選び、移行スクリプトとログイン経路で同一に適用すること。

レインボーテーブルで MD5 ハッシュの元の入力を復元できるか?

復元できるのは、入力がソルトなしでハッシュ化されていて、かつすでにテーブルに載っている場合だけであり、実際上それは短い文字列かありふれた文字列を意味する。長いランダムな入力はこの方法では復元できないし、ユーザーごとのソルトを加えれば事前計算テーブルは完全に無効化される。

MD5 → SHA-1 変換ツールはあるのか?

存在しない。一方向性は MD5 → SHA-1 でも MD5 → SHA-256 と同じように働くからだ。変換ツールを名乗るものは、再ハッシュかルックアップのどちらかでしかない。元の入力が残っているなら SHA-1 ジェネレーターで計算し直せばよく、残っていなければ打つ手はない。

タグ: md5 sha-256 hashing security checksum cryptography