問題点おそらく、パスワード、ファイル、またはAPIキーのハッシュ値を生成するために、組み込みのcryptoモジュールを使用していることでしょう。最初のアイテムについては正しく動作しますが、2つ目を処理しようとした瞬間にアプリケーションがクラッシュします。コンソールには、特定のエラーError [ERR_CRYPTO_HASH_FINALIZED]: Digest already calledが表示されます。
このエラーは通常、1,000個の文字列の配列やログファイルのディレクトリなど、大量のデータを一括処理する際に出現します。これは、すでに処理を終了したハッシュオブジェクトを再利用しようとしているために発生します。
要約:手っ取り早い修正方法Node.jsにおいて、Hashオブジェクトは単発利用のステートマシンです。一度.digest()を呼び出すと、そのオブジェクトは実質的に終了します。それ以上データを追加することはできず、結果を二度求めることもできません。
修正方法: 個々のデータに対して新しいハッシュをインスタンス化してください。crypto.createHash()の呼び出しをループや関数ロジックの内部に移動します。
// ❌ 誤り:複数のアイテムに対して同じオブジェクトを再利用している
const hash = crypto.createHash('sha256');
items.forEach(item => {
const result = hash.update(item).digest('hex'); // 2回目の反復でクラッシュする
});
// ✅ 正解:反復ごとに新しいインスタンスを作成する
items.forEach(item => {
const hash = crypto.createHash('sha256');
const result = hash.update(item).digest('hex');
});
発生理由crypto.Hashクラスは、データをチャンク(塊)で処理するように設計されています。.update()を複数回呼び出して、50MBの大きなファイルの断片を流し込むことができます。しかし、.digest()を呼び出すことは、入力が完了したことを合図します。
この時点で、Node.jsは最終的なビットを計算し、内部バッファをクリアして状態をロックします。この設計は、メモリリークを防ぎ、暗号学的な整合性を保証するためのものです。再度.update()や.digest()を試みると、動作させるためのアクティブなセッションが存在しないため、エンジンはERR_CRYPTO_HASH_FINALIZEDをスローします。
よくあるシナリオ### 1. ループ内でのハッシュ化開発者は、ループの外でハッシャーを宣言することでコードを最適化しようとすることがよくあります。これは単純な変数には有効ですが、暗号化オブジェクトでは失敗します。
// このコードは最初のファイルを処理した後に失敗します
const hasher = crypto.createHash('md5');
const files = ['report_v1.pdf', 'report_v2.pdf'];
const hashes = files.map(f => {
const content = fs.readFileSync(f);
return hasher.update(content).digest('hex');
});
解決策: 常にcreateHashの呼び出しをmapやforEachブロックの中に移動してください。
2. 共有ユーティリティ関数ハッシュインスタンスを引数として渡すと、予期しないクラッシュにつながる可能性があります。ヘルパー関数が.digest()を呼び出すと、そのインスタンスはプログラムの残りの部分では使用できなくなります。
function finalizeHash(hasher, data) {
return hasher.update(data).digest('hex');
}
// 同じ「hasher」を2回渡すと、2回目の呼び出しで失敗します。
検証とデバッグ修正を検証するには、少なくとも3つのアイテムを含む配列でロジックをテストしてください。スクリプトがクラッシュせずに完了すれば、スコープの設定は正解です。また、生成された文字列が業界標準と一致していることも確認する必要があります。
私はNode.jsの出力をダブルチェックするために、よくToolCraftのHash Generatorを使用します。これはデータをアップロードしない、ローカルブラウザツールです。生の文字列をそこに貼り付け、sha256またはmd5を選択し、16進数(hex)出力をコンソールと比較してください。一致していれば、実装は安全で正確です。
予防のためのベストプラクティス- ローカルスコープにする: ハッシュインスタンスは可能な限り最小のスコープ内に保持します。- ヘルパーラッパーを使用する: ハッシュ化ロジックを、最終的な文字列を返す専用の関数にラップします。これにより、その関数呼び出しの中で新しいインスタンスが生成され、破棄されることが保証されます。- モダンなストリーム: 大きなファイルをハッシュ化する場合は、stream/promises APIを使用してください。これにより、ハッシュオブジェクトのライフサイクルが自動的に管理されます。```
const { createHash } = require('node:crypto'); const { pipeline } = require('node:stream/promises'); const { createReadStream } = require('node:fs');
async function hashLargeFile(filePath) { const hash = createHash('sha256'); const source = createReadStream(filePath);
// pipelineが 'end' イベントを処理し、安全にハッシュを確定させます
await pipeline(source, hash);
return hash.digest('hex');
}
上記のパターンを使用すると、ハッシュのライフサイクルをファイルの読み取りストリームに直接結びつけることで、`ERR_CRYPTO_HASH_FINALIZED`を防ぐことができます。ストリームが終了すると、ハッシュは1回限りの最終的な`.digest()`呼び出しの準備が整います。

