🛸

javascriptでhashを扱う

2022/07/09に公開

目的

hashの取り扱いについて、ざっくり学んで行こうと思います。
使用する言語はjavascriptです。
※今回は結果を比較する為、pythonも少し利用します。

環境

OS:Windows 10 Pro
IDE:VSCode バージョン(1.68.1)
docker:Docker version 20.10.16
docker-compose:docker-compose version 1.29.2
docker container:amazon linux 2 + nginx 1.20

hashとは

mozillaの公式に以下の説明がありました

ハッシュ関数は、可変長のメッセージ入力を受けて固定長のハッシュ出力を生成します。 出力は、通常、128ビットの「フィンガープリント」や「メッセージダイジェスト」の形を取ります。 ハッシュは暗号化にとても便利です — 送信データの完全性を保証します。 これはメッセージ認証を提供する HMAC の基礎となります。

詳細はこちら

どういう事?

私の言葉で説明してみます。
「123」をhashしてみます。
→ 「a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3」となります。

「abc」をhashしてみます。
→ 「ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad」となります。

もう一度「123」をhashしてみます。
→ 「a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3」となります。

  • 入力されたデータ(123やabc)に対し、特定のルールに従って不可解な値に変換してくれる。
  • 一度hashしてしまうと入力されたデータが何なのか判別することが非常に困難。
    ※a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3の元値が、123とは推測も判別も非常に難しいです。

※今回はsha256アルゴリズムを使用しました。
※後ほどjavascriptで作成したプログラムを作成します。その際、jsが出力したhash値と比較する為、pythonでhashした値を記載しました。

何に使うんだ?

例えばパスワードに利用してみます。パスワードの値は「Batton@256」とします。

  • パスワードをDBに保存する際、何も手を加えず「Batton@256」と保存すると、万が一DBがハックされ、情報が漏れてしまった場合、悪用される可能性が高まります。
  • パスワードをDBに保存する際、「Batton@256」を「fe375cbad367b5afc88f748d575ef2b8ed53b3c01d51f76ecea78654c3d313e8」にhsashして保存すると、万が一DBがハックされ、情報が漏れてしまった場合でも、パスワードがhashされており、パスワードが何なのか推測することが非常に困難になり、悪用される可能性が低くなります。

hash関数を作ってみる

mozillaに「SubtleCrypto.digest()」関数がありましたので利用してみます。
公式サイトはこちら

hash.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>hash練習</p>

    <p id="hash_value">hash値 = </p>

    <script>
        // mozillaのサンプルをそのまま転用
        const text = '123';

        async function digestMessage(message) {
            const encoder = new TextEncoder();
            const data = encoder.encode(message);
            const hash = await crypto.subtle.digest('SHA-256', data);
            return hash;
        }

        const digestBuffer = await digestMessage(text);
        console.log(digestBuffer.byteLength);
    
        const elem_hash_value = document.getElementById('hash_value');

        // hash値をブラウザ画面に出力してみます
        elem_hash_value.textContent += digestBuffer.byteLength;
    </script>
</body>
</html>


何も出力されません。
あれ?と思い、console.log()の値を確認します。


Uncaught SyntaxErrorが発生していました。

await is only valid in async functions and the top level bodies of modules
awaitがなにやらおかしい。と、教えてくれています。
※awaitは非同期関数とモジュールのトップレベル本体でのみ有効です。

google先生にお伺いを立て、jsを書き換えます。

hash.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>hash練習</p>

    <p id="hash_value">hash値 = </p>

    <script>
        // mozillaのサンプルをそのまま転用
        const text = '123';

        async function digestMessage(message) {
            const encoder = new TextEncoder();
            const data = encoder.encode(message);
            const hash = await crypto.subtle.digest('SHA-256', data);
            return hash;
        }

        (async() => {
            const digestBuffer = await digestMessage(text);
            console.log(digestBuffer.byteLength);

            const elem_hash_value = document.getElementById('hash_value');

            // hash値をブラウザ画面に出力してみます
            elem_hash_value.textContent += digestBuffer.byteLength;
        })();
    </script>
</body>
</html>


hash値 = 32と表示されました。

あれ?不可解な文字列が表示されません。

crypto.subtle.digestはArrayBufferとして返却されるので、16進に変換してね、と公式に記載がありました。

バイナリーから16進へ変換したjsがこちらになります。

hash.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <p>hash練習</p>

    <p id="hash_value">hash値 = </p>

    <script>
        // mozillaのサンプルをそのまま転用
        const text = '123';

        async function digestMessage(message) {
            // const encoder = new TextEncoder();
            // const data = encoder.encode(message);
            // const hash = await crypto.subtle.digest('SHA-256', data);
            // return hash;

            const msgUint8 = new TextEncoder().encode(message);                           // encode as (utf-8) Uint8Array
            const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);           // hash the message
            const hashArray = Array.from(new Uint8Array(hashBuffer));                     // convert buffer to byte array
            const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
            return hashHex;
        }

        (async() => {
            // const digestBuffer = await digestMessage(text);
            // console.log(digestBuffer.byteLength);

            const digestHex = await digestMessage(text);
            console.log(digestHex);

            const elem_hash_value = document.getElementById('hash_value');

            // hash値をブラウザ画面に出力してみます
            // elem_hash_value.textContent += digestBuffer.byteLength;
            elem_hash_value.textContent += digestHex;
        })();

    </script>
</body>
</html>

期待した不可解な文字列が表示されました。

pythonでもjavascriptでもhash値は同じ値になるのか

どういう事?で123をhashしたら
「a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3」
と、出力されました。
こちらはpythonでhashしています。

javascriptで作成したhash関数で123をhashしたら
「a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3」
と、出力されました。
こちらはjavascriptでhashしています。

同じsha256アルゴリズムを利用すれば、言語が違っても同じ入力に対しては同じ値が返ってくる事が解りました。

ありがとうございます。

ご意見、ご批判頂ければ幸いです。

Discussion