1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザだけでSHA-256ハッシュを生成する(Web Crypto / ライブラリ・送信なし)

1
Posted at

ファイルの改ざんチェック、APIの署名確認、文字列の指紋づくり——SHA-256のハッシュ値がサッと欲しい場面は地味に多いです。でも、そのためだけにライブラリを入れたり、入力内容をどこかのオンラインツールに貼る(=送信する)のは、内容によっては気が引けます。

実は、ハッシュ計算はブラウザ標準の Web Crypto API だけで完結します。外部ライブラリも通信も不要です。この記事では、crypto.subtle.digest を使って SHA-256(および SHA-1 / SHA-384 / SHA-512)をブラウザ内で生成する方法と、ハマりやすい注意点をまとめます。

ゴール:文字列やファイルから、ライブラリなし・送信なしで SHA-256 ハッシュを求められるようになること。


最小コード

Web Crypto の crypto.subtle.digest(アルゴリズム, データ) にバイト列を渡すと、ハッシュを ArrayBuffer で返してくれます。文字列はそのまま渡せないので、TextEncoder で UTF-8 のバイト列にしてから渡します。

async function sha256(text) {
  const data = new TextEncoder().encode(text);        // 文字列 → UTF-8 バイト列
  const buf = await crypto.subtle.digest("SHA-256", data); // ArrayBuffer が返る
  return toHex(buf);
}

digest は Promise を返す非同期APIなので、await(または .then)が必要です。ここを忘れて同期的に使おうとすると、Promise { <pending> } が返ってきて「値が取れない」と混乱しがちなので注意。


ArrayBuffer を16進文字列に変換する

digest が返すのは生のバイト列(ArrayBuffer)です。私たちが見慣れた e3b0c4... のような16進文字列にするには、1バイトずつ2桁の16進に変換して連結します。

function toHex(buf) {
  const bytes = new Uint8Array(buf);
  let s = "";
  for (const b of bytes) {
    s += b.toString(16).padStart(2, "0"); // 1桁の値も必ず2桁に(0f など)
  }
  return s;
}

ポイントは padStart(2, "0")。b.toString(16) は 15 → "f" のように1桁になることがあり、これを放置するとハッシュがずれてしまいます。必ず2桁ゼロ埋めします。

ちなみに空文字列の SHA-256 は e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 です。実装が正しいかの確認に使えます。


落とし穴①:crypto.subtle は「セキュアコンテキスト」でないと使えない

これがいちばん多いハマりどころです。crypto.subtle は HTTPS もしくは localhost でないと undefined になります(セキュアコンテキスト限定のAPIのため)。file:// で直接HTMLを開いたり、http:// のローカルIPで開いたりすると動きません。

事前にガードを入れておくと親切です。

if (!("crypto" in window) || !crypto.subtle) {
  // https もしくは localhost で開く必要がある
  alert("この環境では暗号機能が使えません(https または localhost が必要です)");
  return;
}

開発中は localhost、公開時は HTTPS。ここさえ押さえれば大丈夫です。


複数アルゴリズムをまとめて計算する

アルゴリズム名を変えるだけで SHA-1 / SHA-384 / SHA-512 も同じコードで計算できます。Promise.all で並行処理するとスッキリします。

async function digestAll(text, algos = ["SHA-256", "SHA-384", "SHA-512"]) {
  const data = new TextEncoder().encode(text);
  const results = await Promise.all(
    algos.map(async (algo) => {
      const buf = await crypto.subtle.digest(algo, data);
      return [algo, toHex(buf)];
    })
  );
  return Object.fromEntries(results);
}

落とし穴②:MD5 と SHA-1 の扱い

  • MD5 は Web Crypto に含まれません。 ブラウザ標準で使えるのは SHA 系(SHA-1 / SHA-256 / SHA-384 / SHA-512)だけです。MD5 が必要なら別実装が要りますが、そもそも MD5 は壊れているので新規採用は避けるべきです。
  • SHA-1 も非推奨。 衝突攻撃が現実的になっており、セキュリティ用途では使わないでください。レガシー互換の確認くらいに留めます。
  • 新規なら SHA-256 が無難。より強度が欲しければ SHA-384 / SHA-512。

ファイルのハッシュも取れる

文字列だけでなく、ファイルの中身(ArrayBuffer)をそのまま digest に渡せます。ダウンロードしたファイルの整合性チェックなどに便利です。

async function sha256OfFile(file) {
  const buf = await file.arrayBuffer(); // File / Blob → ArrayBuffer
  const hash = await crypto.subtle.digest("SHA-256", buf);
  return toHex(hash);
}

大きなファイルでも、メモリに乗る範囲ならこれで一発です(巨大ファイルをストリーミングで分割ハッシュするのはWeb Cryptoだけでは難しく、別の工夫が要ります)。


重要:パスワードの保存にこのまま使わない

「パスワードを SHA-256 にして保存すれば安全」と思いがちですが、それだけでは不十分です。単純なハッシュは高速なので、総当たりやレインボーテーブルに弱いからです。パスワードを扱うなら、

  • ソルト(ランダムな追加データ)を付ける
  • わざと遅いアルゴリズム(PBKDF2 / bcrypt / scrypt / Argon2)で何万回も繰り返す

のが定石です。Web Crypto なら PBKDF2 が使えます(crypto.subtle.deriveBits + importKey)。SHA-256 の digest 単体は、改ざん検知・指紋づくり・署名検証の部品としては最適ですが、パスワード保存そのものには使わないと覚えておくと安全です。


まとめ

  • ハッシュは Web Crypto(crypto.subtle.digest)だけでブラウザ内生成でき、ライブラリも送信も不要
  • 文字列は TextEncoder で UTF-8 バイト列にしてから渡す
  • 返り値は ArrayBuffer。Uint8Array → toString(16) + padStart(2,"0") で16進化
  • digest は Promise(await 必須)
  • crypto.subtle は https / localhost 限定(file:// やhttpでは undefined)
  • MD5は非対応・SHA-1は非推奨、新規は SHA-256 を
  • ファイルも arrayBuffer() で同じように計算できる
  • パスワード保存にはソルト+PBKDF2等を使う(単純ハッシュは不可)

「ハッシュがちょっと欲しい」に、Web Crypto × ブラウザ完結はとてもよく効きます。参考になれば嬉しいです。


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?