はじめに
業務で初めてNode.jsのcryptoモジュールを使用したので調べて分かったこと・実際に使ったシチュエーションをなどを紹介します。
何のために使用したか?
ある調査のために同一の電話番号が複数のサイトで使われたかどうかを調査する必要がありました。もちろん開発者である自分がセキュリティー、プライバシー観点からマスクされていない顧客の電話番号を直接見ることはできません。
なので実際のログはcustomer電話番号:*************のように出力されているといった具合です。
顧客情報の漏洩を防ぐという点では何ら問題はありませんが、これでは複数サイトで使用されている電話番号が同一かどうかを特定するには不可能です。
顧客情報にはマスクをしつつ、同一の電話番号かを特定可能にする。
それら両方をうまいこと解決するのが今回紹介するcreateHash、createHmacとなります。
そもそも何?
大雑把に言うとnodejsが提供するモジュールの一つという認識で問題ありません。
もっと詳しく知りたい方は以下の公式ドキュメントを参照することをおすすめします。
The
node:cryptomodule provides cryptographic functionality that includes a set of wrappers for OpenSSL’s hash, HMAC, cipher, decipher, sign, and verify functions.(ざっくり意訳)
node:cryptoモジュールは、OpenSSL(昔から使われている超強力な暗号化ライブラリ)が持つハッシュ化、HMAC、暗号化・復号、署名・検証といった機能を、JavaScript から簡単に使えるようにラップして(包んで)提供するものです。
つまり、難解な暗号化の仕組みをゼロから作らなくても、Node.js が用意してくれた機能を使えば、簡単に安全なハッシュ化ができるよ、というわけです。
使い分け
createHash、createHmacこれらの使い分けについては結構シンプルなものです。
結論から言ってしまうと両者の違いは以下のようになります。
createHash: データからハッシュ値を作るcreateHmac: データと「秘密鍵」を混ぜてハッシュ値を作る(秘密鍵が必須)
ハッシュ化・ハッシュ値、後述するsha-256については「わわわIT用語辞典」がわかりやすく解説してくださっているので分からない・気になる方は見てみることをおすすめします。
ハッシュ値は入力された文字が同様の値であれば同様のハッシュ値を返却してくれます。 つまりこの記事の目的でもあったAサイトとBサイトで同様の電話番号が使用されているかどうかの判断が容易になるということです…!
createHash
createHash は、入力されたデータに対して単方向のハッシュ関数(SHA-256など)を適用し、固定長の文字列を生成します。
「データが途中で壊れたり、改ざんされていないか」のチェックなどによく使われます。「チェックサム」とも言われたり、、
import { createHash } from "node:crypto";
const text = "hello world";
// sha256アルゴリズムでハッシュ化し、16進数(hex)で出力するconst hash = createHash("sha256").update(text).digest("hex");
console.log(hash);// -> 8b9d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9このようにハッシュ化しておけば、AサイトとBサイトで同じ 09012345678 が入力された場合、出力されるハッシュ値(8b9d…)は必ず同じになります。これで「元の番号は見えないけれど、同一人物かは分かる」という要件を満たせます。
createHmac
じゃあ createHash だけで十分じゃない?」と思うかもしれません。実は電話番号のような「パターンが限られているデータ」をただハッシュ化するには危険が伴います。
悪意のある人が 00000000000 から 99999999999 までのハッシュ値をあらかじめ計算したリスト(レインボーテーブル)を持っていた場合、出力されたハッシュ値と突き合わせることで元の番号がバレてしまうからです。
そこで登場するのが createHmac です。
createHmac は、ハッシュ化の際に**「秘密鍵(Secret Key)」**を混ぜ込みます。
import { createHmac } from "node:crypto";
const text = "hello world";const secretKey = "super-secret-key"; // 自分と通信相手しか知らない秘密鍵
// sha256アルゴリズムと秘密鍵を使ってハッシュ化し、16進数(hex)で出力するconst hmac = createHmac("sha256", secretKey).update(text).digest("hex");
console.log(hmac);// -> f9b09a7ae91cf02a3a5d8fcd6bc43c2c77d4681fb8e61bcbd24d275fb62ed144秘密鍵を混ぜることでより厳格なセキュリティとなり、「その鍵を知っている人(システム)しか、正しいハッシュ値を作れない」という状態を作ることができます。
今回の「顧客情報(MSN)を安全にマスクしつつ、同一判定をする」という課題に対しても、この createHmac を採用することでよりセキュアに解決することができました。
おまけ:秘密鍵の管理について
実務で扱う場合、この secretKey をソースコードに直接ベタ書きするのは当然ご法度です。
私が実装した際は、AWS Systems Manager (SSM) のパラメーターストアなどの安全な場所に秘密鍵を登録しておき、実行時に動的に取得してハッシュ値の key として扱うようにしました。
暗号化のロジックだけでなく、こういった「鍵の運用面」もセットで設計することが大切ですし様々実務環境によっても異なってくる部分なのでとても重要になりますね。
最後に
「ただハッシュ化すれば安全」と思いがちですが、扱うデータ(今回は電話番号)の特性によっては、適したアプローチが変わってきます。 ぜひ用途に合わせて createHash と createHmac を使い分けてみてください。