Every language
12 lenguajes, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
const bcrypt = require('bcryptjs'); // bcryptjs = pure JS; bcrypt = faster native
async function hashPassword(plain) {
return bcrypt.hash(plain, 12); // $2b$12$<salt+digest> — one self-describing string
}
async function verifyPassword(plain, stored) {
return bcrypt.compare(plain, stored); // boolean; salt+cost parsed from `stored`
}The 12 is the log2 round count — +1 DOUBLES the work (12 ≈ 250 ms of CPU per hash). Never re-hash the input and compare with ===: bcrypt.compare pulls the salt out of the stored string, which is the whole point of the format. argon2id via the argon2 npm package is the modern pick where native builds are acceptable.
TSTypeScript
import { hash, compare } from 'bcryptjs'; // or: import * as argon2 from 'argon2'
export async function hashPassword(plain: string): Promise<string> {
return hash(plain, 12);
}
export async function verifyPassword(plain: string, stored: string): Promise<boolean> {
return compare(plain, stored);
}The stored value is just `string` — no separate salt column — because both bcrypt and argon2 embed salt+params in the output ($2b$12$… vs the PHC string $argon2id$v=19$m=65536,t=3,p=4$…). That self-describing format is the contract; inventing your own salt storage breaks every future migration.
GoGo
import "golang.org/x/crypto/bcrypt"
const passwordCost = 12 // log2 rounds — the ONE place to tune
func HashPassword(pw string) (string, error) {
b, err := bcrypt.GenerateFromPassword([]byte(pw), passwordCost)
return string(b), err
}
func VerifyPassword(pw, stored string) bool {
return bcrypt.CompareHashAndPassword([]byte(stored), []byte(pw)) == nil // nil = match
}bcrypt.DefaultCost is 10 — name your own cost constant in exactly one var so tuning is a one-line change. CompareHashAndPassword returns an error, not a bool: it parses salt+cost from the stored hash; there is no string-compare variant to misuse. argon2id lives in the same module, golang.org/x/crypto/argon2, if you need memory hardness.
RsRust
use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
Argon2,
};
fn hash_password(plain: &str) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
Ok(Argon2::default() // Argon2id v19: m=19456 KiB, t=2, p=1 (OWASP baseline)
.hash_password(plain.as_bytes(), &salt)?
.to_string())
}
fn verify_password(plain: &str, stored: &str) -> Result<bool, argon2::password_hash::Error> {
let parsed = PasswordHash::new(stored)?;
Ok(Argon2::default().verify_password(plain.as_bytes(), &parsed).is_ok())
}to_string() emits the PHC format — $argon2id$v=19$m=19456,t=2,p=1$<b64 salt>$<b64 hash> — and PasswordHash::new parses those params back out on verify, so you never store them separately. Argon2::default() tracks the RustCrypto/OWASP-recommended defaults; override with Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::new(m_kib, t, p, None)?) when you tune.
PHPPHP
$stored = password_hash($pw, PASSWORD_DEFAULT); // argon2id if compiled in, else bcrypt
if (password_verify($pw, $stored)) { // boolean, constant-time inside
if (password_needs_rehash($stored, PASSWORD_DEFAULT)) {
$stored = password_hash($pw, PASSWORD_DEFAULT); // transparent upgrade on login
}
// credentials OK — proceed with the session
}THE canonical password API in any stdlib: PASSWORD_DEFAULT silently moves to stronger algorithms across PHP releases, and password_needs_rehash + re-hash at login is the built-in migration path — no legacy-hash column, no reset campaign. md5($pw), sha1($pw), or crypt() with a hand-rolled salt here are break-ins waiting to be noticed.
PyPython
import hashlib, hmac, os
def hash_password(pw: str) -> str:
salt = os.urandom(16)
dk = hashlib.scrypt(pw.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32)
return f"scrypt$16384$8$1${salt.hex()}${dk.hex()}" # you own this envelope
def verify_password(pw: str, stored: str) -> bool:
algo, n, r, p, salt_hex, dk_hex = stored.split('$')
dk = hashlib.scrypt(pw.encode(), salt=bytes.fromhex(salt_hex),
n=int(n), r=int(r), p=int(p), dklen=len(dk_hex) // 2)
return hmac.compare_digest(dk.hex(), dk_hex) # constant-time, never ==hashlib.scrypt is in the STDLIB (no pip install) but raw: you must persist salt+params yourself, hence the hand-built envelope string — get that format wrong once and old hashes stop verifying. The turnkey routes: argon2-cffi (argon2.hash → PHC string, argon2.verify → bool), the bcrypt package, or passlib wrapping them all behind one API.
C#C#
using Microsoft.AspNetCore.Identity;
var hasher = new PasswordHasher<AppUser>();
string stored = hasher.HashPassword(user, pw); // PBKDF2-HMAC-SHA256, 100k iterations
var result = hasher.VerifyHashedPassword(user, stored, pw);
if (result == PasswordVerificationResult.SuccessRehashNeeded)
stored = hasher.HashPassword(user, pw); // upgrade the old hash at loginPasswordHasher<T> is PBKDF2, not bcrypt — still a tuned-cost KDF — and its output embeds a version byte (V3 = PBKDF2-SHA256/100k; V2 = the legacy weaker format). VerifyHashedPassword returns an enum, and SuccessRehashNeeded IS the rehash-on-login pattern: re-hash when it fires. Non-Identity stacks use BCrypt.Net-Next (BCrypt.Net.BCrypt.HashPassword/Verify).
JvJava
import org.mindrot.jbcrypt.BCrypt;
static String hashPassword(String plain) {
return BCrypt.hashpw(plain, BCrypt.gensalt(12)); // $2a$12$...
}
static boolean verifyPassword(String plain, String stored) {
return BCrypt.checkpw(plain, stored);
}
// Spring Security alternative — the {bcrypt} id prefix rides in the string:
// PasswordEncoder e = PasswordEncoderFactories.createDelegatingPasswordEncoder();
// e.encode(pw); // "{bcrypt}$2a$12$..."
// e.matches(pw, stored); // e.upgradeEncoding(stored) → re-hash on loginOn a Spring stack the DelegatingPasswordEncoder route wins: it stores an {id} prefix ({bcrypt}, {argon2}, {pbkdf2}) so you can adopt a stronger algorithm later without invalidating existing users — upgradeEncoding() flags the rehash-on-login. Raw jBCrypt has no such versioning; note "{bcrypt}$2a$..." and "$2a$..." are different strings, so pick one convention at the start.
SwSwift
import CommonCrypto
import Foundation
func deriveKey(password: String, salt: Data, rounds: UInt32 = 100_000) -> Data {
var derived = Data(repeating: 0, count: 32)
let pw = password.data(using: .utf8)!
let status = derived.withUnsafeMutableBytes { dk in
salt.withUnsafeBytes { s in
pw.withUnsafeBytes { p in
CCKeyDerivationPBKDF(
CCPBKDFAlgorithm(kCCPBKDF2),
p.bindMemory(to: Int8.self).baseAddress, pw.count,
s.bindMemory(to: UInt8.self).baseAddress, salt.count,
CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds,
dk.bindMemory(to: UInt8.self).baseAddress, 32)
}
}
}
precondition(status == kCCSuccess)
return derived
}Honest gap: CryptoKit has HKDF but NO password KDF — no bcrypt/argon2/scrypt anywhere in the framework, so there is no pure-Swift sanctioned answer. CommonCrypto's CCKeyDerivationPBKDF (PBKDF2, 100k+ rounds) is the closest-to-stdlib route, but it returns raw derived bytes: YOU persist salt+rounds beside the hash and compare constant-time. For real password storage, bind the argon2 or bcrypt C libraries via SPM.
KtKotlin
import org.mindrot.jbcrypt.BCrypt
object PasswordHasher {
private const val COST = 12 // log2 rounds; 10..12 is the sane band
fun hash(plain: String): String = BCrypt.hashpw(plain, BCrypt.gensalt(COST))
fun verify(plain: String, stored: String): Boolean = BCrypt.checkpw(plain, stored)
}Same JVM jBCrypt as the Java recipe, wrapped in an object so the cost lives in exactly one const. If Spring is on the classpath, prefer PasswordEncoderFactories.createDelegatingPasswordEncoder() — the {id} prefix convention is what future-proofs storage against an algorithm switch (no mass password reset).
RbRuby
require 'bcrypt'
stored = BCrypt::Password.create(pw, cost: 12) # $2a$12$<salt+digest>
def valid_password?(stored, pw)
BCrypt::Password.new(stored) == pw # == on the Password object, not on strings
endThe == is safe ONLY because the left side is a BCrypt::Password — its == runs bcrypt's constant-time comparison internally, re-deriving with the embedded salt. BCrypt::Password.new(stored) == pw, never stored == computed_hash (timing-leaky) and never BCrypt::Engine.hash(pw) + ===. cost: is the log2 round count; default is 10.
ZigZig
const std = @import("std");
const c = @cImport(@cInclude("argon2.h")); // build.zig: linkSystemLibrary("argon2")
fn hashPassword(allocator: std.mem.Allocator, password: []const u8) ![]u8 {
var salt: [16]u8 = undefined;
std.crypto.random.bytes(&salt);
const enc_len = c.argon2_encodedlen(2, 19456, 1, salt.len, 32, c.Argon2_id);
const out = try allocator.alloc(u8, @intCast(enc_len));
const rc = c.argon2id_hash_encoded(
2, // t: iterations
19456, // m: KiB of memory (the memory-hard knob)
1, // p: parallelism
password.ptr, password.len,
&salt, salt.len,
32, // hash length
out.ptr, enc_len);
if (rc != c.ARGON2_OK) return error.Argon2Failed;
return out; // $argon2id$v=19$m=19456,t=2,p=1$... — params ride in the string
}
fn verifyPassword(password: []const u8, stored: []const u8) bool {
return c.argon2id_verify(stored.ptr, password.ptr, password.len) == c.ARGON2_OK;
}The honest answer for Zig: std.crypto ships hashes, AEADs, even Ed25519 — but NO password KDF (no bcrypt/argon2/scrypt; std.pdb is a debugger format, not a KDF), and there is no stdlib fallback worth showing. The route is this C binding to the reference argon2 implementation; argon2id_verify reads the params back out of the stored PHC string and compares constant-time inside.