troubleshooting2025-04-05·7·128/348

Node.js crypto 모듈 해시 함수 에러

Understanding and fixing common crypto hash function errors in Node.js including encoding issues and algorithm selection.

Node.js crypto 모듈 해시 함수 에러

Introduction

The Node.js crypto module provides cryptographic functionality including hashing, encryption, and signing. Hash function errors can be subtle and hard to debug because they often produce silent data corruption rather than explicit exceptions.

Environment

node --version
# v20.11.0

# Available algorithms
node -e "console.log(require('crypto').getHashes())"

Problem

Hash calculations were producing different results for the same input:

const crypto = require('crypto');

const data = 'Hello, World!';

// These should produce the same hash but don't
const hash1 = crypto.createHash('sha256').update(data).digest('hex');
const hash2 = crypto.createHash('sha256').update(data, 'utf8').digest('hex');

console.log(hash1 === hash2); // false!

Analysis

The issue is that update() without encoding specification uses different defaults depending on the input type. The crypto module handles strings, Buffers, and TypedArrays differently.

Common hash errors:

  1. Encoding mismatch - Different encodings produce different hashes
  2. Algorithm case sensitivity - 'SHA256' vs 'sha256'
  3. Streaming updates - Not finalizing the hash correctly
  4. Binary data handling - Converting strings to bytes incorrectly

Solution

Solution 1: Consistent encoding usage

const crypto = require('crypto');

function createHash(data, algorithm = 'sha256', encoding = 'hex') {
  return crypto.createHash(algorithm)
    .update(data, 'utf8')  // Always specify encoding
    .digest(encoding);
}

const data = 'Hello, World!';
console.log(createHash(data));
// 'dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f'

Solution 2: Handle binary data

const crypto = require('crypto');

// For binary data
const binaryData = Buffer.from([0x00, 0x01, 0x02, 0x03]);
const hash = crypto.createHash('sha256')
  .update(binaryData)  // Buffer is handled correctly
  .digest('hex');

console.log(hash);

// For hex strings
const hexString = '00010203';
const hash2 = crypto.createHash('sha256')
  .update(Buffer.from(hexString, 'hex'))  // Convert hex to Buffer first
  .digest('hex');

Solution 3: Streaming hash for large data

const crypto = require('crypto');
const fs = require('fs');

function hashFile(filePath, algorithm = 'sha256') {
  return new Promise((resolve, reject) => {
    const hash = crypto.createHash(algorithm);
    const stream = fs.createReadStream(filePath);
    
    stream.on('data', (data) => {
      hash.update(data);
    });
    
    stream.on('end', () => {
      resolve(hash.digest('hex'));
    });
    
    stream.on('error', reject);
  });
}

// Usage
hashFile('large-file.zip').then(console.log);

Solution 4: HMAC with proper key handling

const crypto = require('crypto');

function createHMAC(data, key, algorithm = 'sha256') {
  // Key must be a Buffer or string
  const hmac = crypto.createHmac(algorithm, key);
  hmac.update(data, 'utf8');
  return hmac.digest('hex');
}

const data = 'Message to sign';
const secretKey = 'my-secret-key';

console.log(createHMAC(data, secretKey));
// Consistent HMAC output

Solution 5: Password hashing with scrypt

const crypto = require('crypto');
const { promisify } = require('util');

const scryptAsync = promisify(crypto.scrypt);

async function hashPassword(password) {
  const salt = crypto.randomBytes(16).toString('hex');
  const derivedKey = await scryptAsync(password, salt, 64);
  return `${salt}:${derivedKey.toString('hex')}`;
}

async function verifyPassword(password, storedHash) {
  const [salt, hash] = storedHash.split(':');
  const derivedKey = await scryptAsync(password, salt, 64);
  return derivedKey.toString('hex') === hash;
}

// Usage
(async () => {
  const hash = await hashPassword('my-password');
  console.log('Hash:', hash);
  
  const isMatch = await verifyPassword('my-password', hash);
  console.log('Match:', isMatch);
})();

Lessons Learned

  1. Always specify encoding in update() calls - Do not rely on defaults
  2. Use lowercase algorithm names - 'sha256' not 'SHA256'
  3. Handle binary data as Buffers - Do not convert to strings
  4. Use streaming for large data - Avoid loading entire files into memory
  5. Use scrypt or bcrypt for passwords - Never use raw SHA256 for password hashing

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.