Hashing in 2026: SHA-256, HMAC and PBKDF2 across Node, Python, Go, Rust
Which stdlib function to reach for, which library to avoid, and the one-liner that gets you SHA-256 + HMAC-SHA256 in each of the big runtimes.
# The short version
SHA-256 is the 2026 default for general-purpose hashing. HMAC-SHA256 is the 2026 default for message authentication. Argon2id (or bcrypt) is the 2026 default for passwords. Below are the idiomatic one-liners for each.
<div class="callout callout-warning" role="note"><div class="callout-title">Warning</div><div class="callout-body"><p>Never use plain SHA-256 to hash passwords. SHA-2 is designed to be fast, which is exactly the opposite of what password hashing needs. Use argon2id or bcrypt — they're designed to resist GPU brute-force.</p></div></div>
# Node.js / JavaScript
import { createHash, createHmac, pbkdf2Sync } from "node:crypto";
// SHA-256 hex digest
createHash("sha256").update("hello").digest("hex");
// '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'
// HMAC-SHA256
createHmac("sha256", "secret").update("hello").digest("hex");
// 'f31235...'
// PBKDF2 (not ideal for passwords, but stdlib)
pbkdf2Sync("password", "salt", 600_000, 32, "sha256").toString("hex");
For argon2, reach for @node-rs/argon2 or argon2:
import { hash, verify } from "@node-rs/argon2";
const h = await hash("password"); // stores salt + params inside
await verify(h, "password"); // → true
In the browser, SubtleCrypto is the equivalent — async, hex-awkward:
const bytes = new TextEncoder().encode("hello");
const digest = await crypto.subtle.digest("SHA-256", bytes);
const hex = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, "0")).join("");
# Python
Stdlib via hashlib and hmac. Argon2 needs argon2-cffi.
import hashlib, hmac
hashlib.sha256(b"hello").hexdigest()
# '2cf24dba...'
hmac.new(b"secret", b"hello", hashlib.sha256).hexdigest()
# 'f31235...'
# PBKDF2 — stdlib
hashlib.pbkdf2_hmac("sha256", b"password", b"salt", 600_000).hex()
For passwords in 2026, use argon2-cffi:
from argon2 import PasswordHasher
ph = PasswordHasher()
h = ph.hash("password") # stores salt + params
ph.verify(h, "password") # raises on mismatch
# Go
Stdlib via crypto/sha256 and crypto/hmac. Argon2 lives in golang.org/x/crypto/argon2.
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// SHA-256
h := sha256.Sum256([]byte("hello"))
hex.EncodeToString(h[:])
// "2cf24dba..."
// HMAC-SHA256
mac := hmac.New(sha256.New, []byte("secret"))
mac.Write([]byte("hello"))
hex.EncodeToString(mac.Sum(nil))
// "f31235..."
<div class="callout callout-tip" role="note"><div class="callout-title">Tip</div><div class="callout-body"><p>Use <code>hmac.Equal</code> to compare MACs, not <code>==</code> or <code>bytes.Equal</code>. It's constant-time — defeats timing attacks that can leak the signature byte-by-byte.</p></div></div>
# Rust
Most ecosystems use the sha2 + hmac crates from the RustCrypto organisation.
use sha2::{Sha256, Digest};
use hmac::{Hmac, Mac};
// SHA-256
let mut h = Sha256::new();
h.update(b"hello");
let digest = h.finalize();
hex::encode(digest);
// "2cf24dba..."
// HMAC-SHA256
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_from_slice(b"secret").unwrap();
mac.update(b"hello");
let sig = mac.finalize().into_bytes();
hex::encode(sig);
// "f31235..."
For passwords, argon2:
use argon2::{Argon2, PasswordHasher, PasswordVerifier, password_hash::SaltString};
use rand::rngs::OsRng;
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let hash = argon2.hash_password(b"password", &salt).unwrap().to_string();
# Browser (no Node)
Only stdlib available is SubtleCrypto, async-only.
// SHA-256
const bytes = new TextEncoder().encode("hello");
const digest = await crypto.subtle.digest("SHA-256", bytes);
// HMAC-SHA256
const key = await crypto.subtle.importKey(
"raw", new TextEncoder().encode("secret"),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode("hello"));
Our Hash Generator and HMAC Generator use exactly this API — everything runs client-side, your secrets never touch a server.
# Quick comparison
| Task | Node | Python | Go | Rust |
|-------------------|-----------------|----------------|-----------------|---------------------|
| SHA-256 | createHash | hashlib.sha256| sha256.Sum256 | Sha256::new |
| HMAC-SHA256 | createHmac | hmac.new | hmac.New | Hmac::new_from_slice |
| PBKDF2 | pbkdf2Sync | pbkdf2_hmac | pbkdf2.Key | pbkdf2::pbkdf2 |
| Argon2 (preferred)| @node-rs/argon2| argon2-cffi | x/crypto/argon2| argon2 crate |
# Common bugs
1. Using === or == to compare HMACs. Timing-safe comparison only. Node's crypto.timingSafeEqual, Go's hmac.Equal, Python's hmac.compare_digest, Rust's subtle::ConstantTimeEq.
2. Forgetting to encode strings before hashing. Every SHA-256 implementation takes bytes, not strings. UTF-8 decode explicitly.
3. Reusing the HMAC key object between calls. Some implementations are stateful; always construct a fresh mac per message.
4. Hashing passwords with SHA-256. Fast = bad for passwords. A $500 GPU cracks billions of SHA-256 guesses per second.
# Verify it
Drop any text into our Hash Generator — SHA-1, 256, 384, 512 computed locally in your browser. For signed authentication, HMAC Generator takes a secret + message.
# Related reading
Frequently asked questions
›Should I still use SHA-1?
Not for signatures or content-addressed IDs. For HMAC (which doesn't need collision resistance) it's fine but obsolete — SHA-256 is just as fast on modern CPUs. Git still uses SHA-1 internally, which is why it's pinned to strict enforcement mode.
›Is MD5 ever okay?
As a non-security hash (cache keys, bloom filters, ETags) — yes, it's fast and fine. For passwords, signatures, or integrity checks against a motivated attacker — absolutely not.
›What about bcrypt / argon2 for passwords?
Different category. Those are password-hashing functions designed to be slow and memory-hard. SHA-256 is a general-purpose hash — fast enough that an attacker with a GPU can brute-force your password hashes. Use argon2id for new code.