Khắc phục lỗi 'ERR_CRYPTO_HASH_FINALIZED: Digest already called' trong Node.js

intermediate💚 Node.js2026-07-05| Node.js (Tất cả các phiên bản), Crypto Module, Windows/Linux/macOS

Error Message

Error [ERR_CRYPTO_HASH_FINALIZED]: Digest already called
#nodejs#crypto#backend#javascript

Vấn đềBạn có thể đang sử dụng module crypto tích hợp sẵn để tạo mã băm (hash) cho mật khẩu, tệp hoặc API key. Mã của bạn hoạt động tốt với mục đầu tiên, nhưng ngay khi xử lý mục thứ hai, ứng dụng bị crash. Console hiển thị một lỗi cụ thể: Error [ERR_CRYPTO_HASH_FINALIZED]: Digest already called.

Lỗi này thường xuất hiện khi xử lý một loạt dữ liệu, chẳng hạn như một mảng gồm 1.000 chuỗi hoặc một thư mục chứa các tệp log. Nó xảy ra vì bạn đang cố gắng sử dụng một đối tượng hash đã hoàn thành nhiệm vụ của nó.

TL;DR: Cách khắc phục nhanhTrong Node.js, một đối tượng Hash là một máy trạng thái sử dụng một lần (single-use state machine). Một khi bạn gọi .digest(), đối tượng đó coi như đã kết thúc. Bạn không thể thêm dữ liệu và cũng không thể yêu cầu kết quả lần thứ hai.

Cách khắc phục: Khởi tạo một hash mới cho mỗi phần dữ liệu riêng biệt. Hãy di chuyển lệnh gọi crypto.createHash() vào bên trong vòng lặp hoặc logic hàm của bạn.

// ❌ SAI: Tái sử dụng cùng một đối tượng cho nhiều mục
const hash = crypto.createHash('sha256');
items.forEach(item => {
    const result = hash.update(item).digest('hex'); // Bị crash ở lần lặp thứ 2
});

// ✅ ĐÚNG: Tạo một instance mới cho mỗi lần lặp
items.forEach(item => {
    const hash = crypto.createHash('sha256');
    const result = hash.update(item).digest('hex');
});

Tại sao lỗi này xảy raLớp crypto.Hash được thiết kế để xử lý dữ liệu theo từng phần (chunks). Bạn có thể gọi .update() nhiều lần để đưa vào các đoạn của một tệp lớn 50MB. Tuy nhiên, việc gọi .digest() là tín hiệu cho biết đầu vào đã hoàn tất.

Tại thời điểm này, Node.js tính toán các bit cuối cùng, xóa các buffer nội bộ và khóa trạng thái. Thiết kế này giúp ngăn chặn rò rỉ bộ nhớ và đảm bảo tính toàn vẹn của mật mã. Nếu bạn cố gắng .update() hoặc .digest() một lần nữa, engine sẽ ném ra lỗi ERR_CRYPTO_HASH_FINALIZED vì không còn phiên làm việc (session) nào đang hoạt động.

Các kịch bản phổ biến### 1. Tạo Hash trong vòng lặpCác lập trình viên thường cố gắng tối ưu hóa mã bằng cách khai báo hasher bên ngoài vòng lặp. Mặc dù cách này hoạt động với các biến đơn giản, nhưng nó sẽ thất bại với các đối tượng mật mã.

// Mã này sẽ thất bại sau khi xử lý tệp đầu tiên
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'); 
});

Giải pháp: Luôn di chuyển lệnh gọi createHash vào bên trong khối map hoặc forEach.

2. Các hàm tiện ích dùng chungViệc truyền một instance hash dưới dạng tham số có thể dẫn đến các lỗi crash không mong muốn. Nếu một hàm bổ trợ gọi .digest(), instance đó sẽ trở nên vô dụng đối với phần còn lại của chương trình.

function finalizeHash(hasher, data) {
    return hasher.update(data).digest('hex');
}

// Nếu bạn truyền cùng một 'hasher' vào đây hai lần, lần gọi thứ hai sẽ thất bại.

Xác minh và Gỡ lỗiĐể xác minh bản sửa lỗi, hãy kiểm tra logic của bạn với một mảng có ít nhất ba mục. Nếu script hoàn thành mà không bị crash, phạm vi (scoping) của bạn đã chính xác. Bạn cũng nên xác minh rằng các chuỗi được tạo ra khớp với các tiêu chuẩn ngành mong đợi.

Tôi thường sử dụng Hash Generator trên ToolCraft để kiểm tra chéo kết quả đầu ra của Node.js. Đây là một công cụ trình duyệt chạy cục bộ và không tải dữ liệu của bạn lên máy chủ. Dán chuỗi thô của bạn vào đó, chọn sha256 hoặc md5, và so sánh đầu ra hex với console của bạn. Nếu chúng khớp nhau, triển khai của bạn là an toàn và chính xác.

Các thực hành tốt nhất để phòng ngừa- Phạm vi cục bộ (Scope Locally): Giữ các instance hash bên trong phạm vi nhỏ nhất có thể.- Sử dụng Helper Wrappers: Gói logic hashing trong một hàm chuyên dụng trả về chuỗi cuối cùng. Điều này đảm bảo một instance mới được sinh ra và kết thúc ngay trong lần gọi hàm đó.- Stream hiện đại: Khi hashing các tệp lớn, hãy sử dụng API stream/promises. API này tự động quản lý vòng đời của đối tượng hash.```

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 xử lý sự kiện 'end' và kết thúc hash một cách an toàn
await pipeline(source, hash);
return hash.digest('hex');

}


Sử dụng mẫu trên giúp ngăn chặn `ERR_CRYPTO_HASH_FINALIZED` bằng cách gắn vòng đời của hash trực tiếp với read stream của tệp. Khi stream kết thúc, hash đã sẵn sàng cho một lần gọi `digest()` cuối cùng duy nhất.

Related Error Notes