[{"data":1,"prerenderedAt":821},["ShallowReactive",2],{"blog-salting-and-hashing-developers-guide":3},{"id":4,"title":5,"alt":6,"author":7,"body":8,"category":794,"description":795,"extension":796,"faq":797,"image":807,"meta":808,"navigation":541,"path":809,"publishedAt":810,"seo":811,"stem":812,"tags":813,"__hash__":820},"blog\u002Fen\u002Fsalting-and-hashing-developers-guide.md","Salting and Hashing: A Developer's Practical Guide (2026)","password hashing and salting developer guide — bcrypt argon2 comparison","Alex Vibe, Senior Security Dev",{"type":9,"value":10,"toc":775},"minimark",[11,15,18,23,26,29,33,41,49,59,62,69,75,79,82,235,238,242,245,248,263,270,273,334,337,340,344,347,353,364,370,373,395,399,402,405,408,419,425,429,435,449,463,483,504,510,514,521,649,660,664,669,677,705,709,714,721,725,728,732,735,739,742,746,749,753,756,762,765,771],[12,13,14],"p",{},"If your database gets dumped tonight — and statistically, it will eventually — the question isn't whether attackers get your users' password hashes. They will. The question is whether those hashes are crackable before your users can rotate their credentials.",[12,16,17],{},"That answer comes down to one decision you made (or didn't make) when you wrote your user registration endpoint: which hashing algorithm did you use, and did you salt it correctly?",[19,20,22],"h2",{"id":21},"what-password-hashing-actually-is","What Password Hashing Actually Is",[12,24,25],{},"A cryptographic hash function takes arbitrary input and produces a fixed-length output (the digest). It's deterministic — same input always produces same output — and one-way: you can't reverse it. MD5 produces 128-bit digests. SHA-256 produces 256-bit digests. Neither is suitable for passwords. Not because they're broken as hash functions, but because they're too fast.",[12,27,28],{},"Speed is the enemy here. A hash designed for checksums or digital signatures should complete in microseconds. A hash protecting a user's password needs to take tens or hundreds of milliseconds — deliberately, by design. The difference between \"fast\" and \"slow\" hashing is the difference between a breach that costs you a PR crisis and one that costs your users their bank accounts.",[19,30,32],{"id":31},"why-salts-exist-and-what-they-actually-prevent","Why Salts Exist (And What They Actually Prevent)",[12,34,35,36,40],{},"Before salting, attackers pre-computed massive lookup tables mapping common passwords to their MD5\u002FSHA-1 digests — rainbow tables. You could crack ",[37,38,39],"code",{},"password123","'s MD5 hash instantly with a table lookup. No GPU required.",[12,42,43,44,48],{},"A ",[45,46,47],"strong",{},"salt"," is a random byte string generated uniquely per user, prepended to the password before hashing:",[50,51,56],"pre",{"className":52,"code":54,"language":55},[53],"language-text","hash = H(salt || password)\n","text",[37,57,54],{"__ignoreMap":58},"",[12,60,61],{},"The salt is stored in plaintext alongside the hash. That's intentional. Its job isn't to be secret — it's to make rainbow tables computationally useless. With a 16-byte random salt, an attacker would need a separate rainbow table for every possible salt value. That's 2^128 tables. Not happening.",[12,63,64],{},[65,66],"img",{"alt":67,"src":68},"Password hashing flow showing salt generation, concatenation, and digest output","\u002Fimages\u002Fblog\u002Fsalting-and-hashing-developers-guide\u002Fhashing-flow.webp",[12,70,71,74],{},[45,72,73],{},"What salting doesn't prevent:"," It doesn't stop an attacker from brute-forcing your hashes one at a time. That's where algorithm choice matters.",[19,76,78],{"id":77},"the-algorithm-hierarchy","The Algorithm Hierarchy",[12,80,81],{},"Not all hashing algorithms are created equal. Here's where each one lands in 2026:",[83,84,85,107],"table",{},[86,87,88],"thead",{},[89,90,91,95,98,101,104],"tr",{},[92,93,94],"th",{},"Algorithm",[92,96,97],{},"Type",[92,99,100],{},"RTX 4090 Speed",[92,102,103],{},"Suitable for Passwords?",[92,105,106],{},"Notes",[108,109,110,130,147,164,181,200,217],"tbody",{},[89,111,112,116,119,122,127],{},[113,114,115],"td",{},"MD5",[113,117,118],{},"General-purpose",[113,120,121],{},"~164 billion\u002Fsec",[113,123,124],{},[45,125,126],{},"No",[113,128,129],{},"Cryptographically broken; collision attacks exist",[89,131,132,135,137,140,144],{},[113,133,134],{},"SHA-1",[113,136,118],{},[113,138,139],{},"~91 billion\u002Fsec",[113,141,142],{},[45,143,126],{},[113,145,146],{},"Collision-broken by SHAttered (2017)",[89,148,149,152,154,157,161],{},[113,150,151],{},"SHA-256",[113,153,118],{},[113,155,156],{},"~23 billion\u002Fsec",[113,158,159],{},[45,160,126],{},[113,162,163],{},"Secure as a hash; catastrophic for passwords",[89,165,166,169,171,174,178],{},[113,167,168],{},"SHA-512",[113,170,118],{},[113,172,173],{},"~9 billion\u002Fsec",[113,175,176],{},[45,177,126],{},[113,179,180],{},"Still 9 billion guesses\u002Fsec is not \"slow enough\"",[89,182,183,186,189,192,197],{},[113,184,185],{},"bcrypt",[113,187,188],{},"KDF",[113,190,191],{},"~184,000\u002Fsec",[113,193,194],{},[45,195,196],{},"Yes",[113,198,199],{},"Cost factor tunable; 60+ year default; widely supported",[89,201,202,205,207,210,214],{},[113,203,204],{},"scrypt",[113,206,188],{},[113,208,209],{},"~100,000\u002Fsec",[113,211,212],{},[45,213,196],{},[113,215,216],{},"Memory-hard; stronger than bcrypt against ASICs",[89,218,219,222,224,227,232],{},[113,220,221],{},"Argon2id",[113,223,188],{},[113,225,226],{},"~15,000\u002Fsec",[113,228,229],{},[45,230,231],{},"Yes (preferred)",[113,233,234],{},"NIST SP 800-63B recommended; memory + time hard",[12,236,237],{},"The takeaway is blunt: if you're using any SHA variant to hash passwords, you have a critical vulnerability. Fix it before you finish reading this.",[19,239,241],{"id":240},"the-math-why-rtx-4090-benchmarks-define-your-minimum-work-factor","The Math: Why RTX 4090 Benchmarks Define Your Minimum Work Factor",[12,243,244],{},"The entropy formula for passwords is:",[12,246,247],{},"$H = L \\times \\log_2(R)$",[12,249,250,251,254,255,258,259,262],{},"Where ",[45,252,253],{},"H"," = entropy in bits, ",[45,256,257],{},"L"," = password length, ",[45,260,261],{},"R"," = character pool size (charset).",[12,264,265,266,269],{},"A 12-character password using full ASCII (95 printable chars) gives H = 12 × log₂(95) ≈ ",[45,267,268],{},"78.8 bits"," of entropy. That's the theoretical search space. The practical question is how quickly an attacker can traverse it.",[12,271,272],{},"Against bcrypt at cost factor 12 (~184,000 hashes\u002Fsec on RTX 4090):",[83,274,275,288],{},[86,276,277],{},[89,278,279,282,285],{},[92,280,281],{},"Password Entropy",[92,283,284],{},"bcrypt Time to Crack (RTX 4090)",[92,286,287],{},"SHA-256 Time to Crack (RTX 4090)",[108,289,290,301,312,323],{},[89,291,292,295,298],{},[113,293,294],{},"40 bits (weak)",[113,296,297],{},"~70 minutes",[113,299,300],{},"~0.05 seconds",[89,302,303,306,309],{},[113,304,305],{},"60 bits (fair)",[113,307,308],{},"~200 years",[113,310,311],{},"~14 hours",[89,313,314,317,320],{},[113,315,316],{},"78 bits (strong)",[113,318,319],{},"~28 million years",[113,321,322],{},"~188 years",[89,324,325,328,331],{},[113,326,327],{},"100 bits (very strong)",[113,329,330],{},"Effectively infinite",[113,332,333],{},"~800,000 years",[12,335,336],{},"Same password. Completely different outcomes. That's the algorithm choice made tangible.",[338,339],"entropy-calculator",{},[19,341,343],{"id":342},"how-bcrypt-works-and-what-the-cost-factor-means","How bcrypt Works (And What the Cost Factor Means)",[12,345,346],{},"bcrypt was designed in 1999 by Niels Provos and David Mazières specifically for password hashing. It generates a random 128-bit salt internally, runs the Eksblowfish key schedule, and embeds both the salt and cost factor in its output string:",[50,348,351],{"className":349,"code":350,"language":55},[53],"$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8\u002FLeAi9O5p2mC...\n",[37,352,350],{"__ignoreMap":58},[12,354,355,356,359,360,363],{},"Breaking that down: ",[37,357,358],{},"$2b$"," is the algorithm version, ",[37,361,362],{},"12"," is the cost factor, and the rest is the base64-encoded salt + digest.",[12,365,366,369],{},[45,367,368],{},"The cost factor is logarithmic."," Cost 12 means 2^12 = 4,096 rounds of the Eksblowfish key setup. Bump it to 13 and you double the computation time. Bump it to 14 and you double it again. Your target should be a cost factor that takes ~100–300ms on your server hardware. Re-evaluate this every 18 months as hardware improves.",[12,371,372],{},"Current 2026 recommendations:",[374,375,376,383,389],"ul",{},[377,378,379,382],"li",{},[45,380,381],{},"bcrypt:"," cost factor 13–14",[377,384,385,388],{},[45,386,387],{},"Argon2id:"," memory=64MB, iterations=3, parallelism=4",[377,390,391,394],{},[45,392,393],{},"scrypt:"," N=32768, r=8, p=1",[19,396,398],{"id":397},"why-argon2id-wins-in-2026","Why Argon2id Wins in 2026",[12,400,401],{},"Argon2id is the hybrid variant of Argon2 — it runs Argon2i (side-channel resistant) in the first half and Argon2d (GPU-resistant) in the second. The memory hardness is the key differentiator.",[12,403,404],{},"bcrypt's memory footprint is fixed at 4KB. An attacker can run thousands of bcrypt instances in parallel on a single GPU because each instance fits entirely in GPU cache. Argon2id with 64MB memory cost doesn't fit in GPU cache — it requires actual memory bandwidth, which GPUs don't have at the same ratio as CPUs. You're forcing the attacker onto hardware that's orders of magnitude less efficient for this specific task.",[12,406,407],{},"NIST SP 800-63B Section 5.1.1.2 explicitly recommends memory-hard KDFs for password storage. Argon2id satisfies that requirement. bcrypt does not, technically — but it's still far better than any general-purpose hash.",[12,409,410,411,418],{},"Use our ",[45,412,413],{},[414,415,417],"a",{"href":416},"\u002Fhash-generator","Hash Generator"," — runs 100% in your browser, zero data sent to any server — to verify hash outputs and test HMAC signatures for your API authentication workflows.",[12,420,421],{},[65,422],{"alt":423,"src":424},"Argon2id memory-hard computation diagram showing parallel GPU limitation vs CPU advantage","\u002Fimages\u002Fblog\u002Fsalting-and-hashing-developers-guide\u002Fargon2id-memory-hard.webp",[19,426,428],{"id":427},"common-implementation-mistakes","Common Implementation Mistakes",[12,430,431,434],{},[45,432,433],{},"Mistake 1: Using a global salt."," Some devs generate one salt and use it for every user. This defeats salting entirely — two users with the same password get the same hash. Per-user, per-hash random salts only.",[12,436,437,440,441,444,445,448],{},[45,438,439],{},"Mistake 2: Hashing before salting."," ",[37,442,443],{},"H(password)"," + salt stored separately is not salted hashing. The salt must be input to the hash function: ",[37,446,447],{},"H(salt || password)",". Every KDF library handles this correctly if you use the library's built-in salt generation. Don't roll your own.",[12,450,451,454,455,458,459,462],{},[45,452,453],{},"Mistake 3: Truncating bcrypt input."," bcrypt silently truncates passwords longer than 72 bytes. If you're pre-hashing with SHA-256 to work around this, use ",[37,456,457],{},"bcrypt(base64(sha256(password)))"," — not raw SHA-256 bytes. Here's why base64 specifically: a raw SHA-256 digest is 32 bytes of arbitrary binary data, and any of those bytes could be ",[37,460,461],{},"0x00",". bcrypt's C-string internals treat null bytes as string terminators, so a password whose SHA-256 digest happens to start with a null byte would be hashed as an empty string. Base64-encoding the digest first eliminates null bytes entirely from the input. It's a subtle attack surface and the kind of thing that silently destroys your security without any error.",[12,464,465,440,472,474,475,478,479,482],{},[45,466,467,468,471],{},"Mistake 4: Using ",[37,469,470],{},"Math.random()"," for salt generation.",[37,473,470],{}," is a deterministic PRNG seeded from system time. It's not cryptographically secure. Your salt generator needs ",[37,476,477],{},"crypto.getRandomValues()"," on the frontend or ",[37,480,481],{},"crypto.randomBytes()"," in Node.js — the same entropy source that your OS uses for TLS keys.",[484,485,486],"blockquote",{},[12,487,488,489,491,492,496,497,500,501,503],{},"\"Avoid tools that use ",[37,490,470],{},". Our ",[414,493,495],{"href":494},"\u002F","Password Generator"," uses the ",[45,498,499],{},"Web Crypto API"," (",[37,502,477],{},"), ensuring your entropy source is as secure as your operating system's kernel.\"",[12,505,506,509],{},[45,507,508],{},"Mistake 5: Not re-hashing on login."," If you're upgrading your algorithm (say, from bcrypt cost 10 to Argon2id), you can't re-hash stored passwords — you don't have the plaintexts. The correct approach: on successful login, check if the stored hash uses the old algorithm\u002Fparameters, and if so, re-hash with the new parameters and update the stored value. Gradual migration, zero disruption.",[19,511,513],{"id":512},"how-to-implement-argon2id-in-nodejs","How to Implement Argon2id in Node.js",[12,515,516,517,520],{},"Copy-paste ready. The ",[37,518,519],{},"argon2"," npm package wraps the reference C implementation:",[50,522,526],{"className":523,"code":524,"language":525,"meta":58,"style":58},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import argon2 from \"argon2\";\n\n\u002F\u002F Hash on registration\nconst hash = await argon2.hash(password, {\n  type: argon2.argon2id,\n  memoryCost: 2 ** 16, \u002F\u002F 64 MB\n  timeCost: 3,         \u002F\u002F iterations\n  parallelism: 4,\n});\n\u002F\u002F Store `hash` in your database — it embeds salt + params in the string\n\n\u002F\u002F Verify on login\nconst valid = await argon2.verify(hash, password);\nif (!valid) throw new Error(\"Invalid credentials\");\n\n\u002F\u002F Rehash check — upgrade parameters transparently on successful login\nif (argon2.needsRehash(hash, { memoryCost: 2 ** 16, timeCost: 3 })) {\n  const newHash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 2 ** 16, timeCost: 3 });\n  await db.updateUserHash(userId, newHash);\n}\n","javascript",[37,527,528,536,543,549,555,561,567,573,579,585,591,596,602,608,614,619,625,631,637,643],{"__ignoreMap":58},[529,530,533],"span",{"class":531,"line":532},"line",1,[529,534,535],{},"import argon2 from \"argon2\";\n",[529,537,539],{"class":531,"line":538},2,[529,540,542],{"emptyLinePlaceholder":541},true,"\n",[529,544,546],{"class":531,"line":545},3,[529,547,548],{},"\u002F\u002F Hash on registration\n",[529,550,552],{"class":531,"line":551},4,[529,553,554],{},"const hash = await argon2.hash(password, {\n",[529,556,558],{"class":531,"line":557},5,[529,559,560],{},"  type: argon2.argon2id,\n",[529,562,564],{"class":531,"line":563},6,[529,565,566],{},"  memoryCost: 2 ** 16, \u002F\u002F 64 MB\n",[529,568,570],{"class":531,"line":569},7,[529,571,572],{},"  timeCost: 3,         \u002F\u002F iterations\n",[529,574,576],{"class":531,"line":575},8,[529,577,578],{},"  parallelism: 4,\n",[529,580,582],{"class":531,"line":581},9,[529,583,584],{},"});\n",[529,586,588],{"class":531,"line":587},10,[529,589,590],{},"\u002F\u002F Store `hash` in your database — it embeds salt + params in the string\n",[529,592,594],{"class":531,"line":593},11,[529,595,542],{"emptyLinePlaceholder":541},[529,597,599],{"class":531,"line":598},12,[529,600,601],{},"\u002F\u002F Verify on login\n",[529,603,605],{"class":531,"line":604},13,[529,606,607],{},"const valid = await argon2.verify(hash, password);\n",[529,609,611],{"class":531,"line":610},14,[529,612,613],{},"if (!valid) throw new Error(\"Invalid credentials\");\n",[529,615,617],{"class":531,"line":616},15,[529,618,542],{"emptyLinePlaceholder":541},[529,620,622],{"class":531,"line":621},16,[529,623,624],{},"\u002F\u002F Rehash check — upgrade parameters transparently on successful login\n",[529,626,628],{"class":531,"line":627},17,[529,629,630],{},"if (argon2.needsRehash(hash, { memoryCost: 2 ** 16, timeCost: 3 })) {\n",[529,632,634],{"class":531,"line":633},18,[529,635,636],{},"  const newHash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 2 ** 16, timeCost: 3 });\n",[529,638,640],{"class":531,"line":639},19,[529,641,642],{},"  await db.updateUserHash(userId, newHash);\n",[529,644,646],{"class":531,"line":645},20,[529,647,648],{},"}\n",[12,650,651,652,655,656,659],{},"The ",[37,653,654],{},"needsRehash"," call is your migration path. Next time you increase ",[37,657,658],{},"memoryCost",", existing users get silently upgraded on their next successful login. No mass re-hash job, no downtime.",[19,661,663],{"id":662},"verifying-hash-integrity-in-practice","Verifying Hash Integrity in Practice",[12,665,651,666,668],{},[414,667,417],{"href":416}," on this site supports SHA-256, SHA-512, HMAC, and file hashing — all client-side. It's useful for verifying API payload signatures, testing HMAC-SHA256 outputs for webhook validation, and checking file checksums without uploading anything.",[12,670,671,672,676],{},"For password strength before it ever reaches your hash function, the ",[414,673,675],{"href":674},"\u002Fpassword-strength-checker","Password Strength Checker"," calculates entropy bits and offline crack-time estimates against bcrypt and MD5 benchmarks. Run it on your test passwords before your next deploy.",[484,678,679,684,687],{},[12,680,681],{},[45,682,683],{},"🛡️ Security Checkpoint — Complete This Step",[12,685,686],{},"If your user table was dumped right now, your algorithm choice determines how much time your users have to respond. Check your stack before it's not hypothetical.",[374,688,689,695,700],{},[377,690,691,692,694],{},"→ ",[414,693,417],{"href":416}," — verify your HMAC and SHA outputs client-side, no uploads",[377,696,691,697,699],{},[414,698,675],{"href":674}," — see entropy bits and bcrypt crack-time estimates live",[377,701,691,702,704],{},[414,703,495],{"href":494}," — generate high-entropy passwords worth protecting with Argon2id",[19,706,708],{"id":707},"frequently-asked-questions","Frequently Asked Questions",[710,711,713],"h3",{"id":712},"what-is-the-difference-between-hashing-and-salting","What is the difference between hashing and salting?",[12,715,716,717,720],{},"Hashing is a one-way transformation — a deterministic function that converts any input into a fixed-length digest. Salting adds a unique random value (the salt) to each password before it's hashed. Without salts, two users with the password ",[37,718,719],{},"hunter2"," produce identical hashes, enabling precomputed rainbow table lookups. With unique per-user salts, each hash is unique even for identical plaintext passwords. The salt is stored in plaintext alongside the hash — its job is computational cost, not secrecy.",[710,722,724],{"id":723},"is-sha-256-safe-for-password-hashing","Is SHA-256 safe for password hashing?",[12,726,727],{},"No, and the reason is pure arithmetic. SHA-256 is designed to be fast — it's used for digital signatures, TLS handshakes, and blockchain proofs, where speed matters. An RTX 4090 computes approximately 23 billion SHA-256 hashes per second. A 10-character password using uppercase, lowercase, and digits (62 chars) has a search space of 62^10 ≈ 8.4 × 10^17. At 23 billion hashes\u002Fsec, that's about 37 million seconds — roughly 14 months. With a GPU cluster? Days. Use Argon2id.",[710,729,731],{"id":730},"what-is-argon2id-and-why-is-it-the-2026-recommendation","What is Argon2id and why is it the 2026 recommendation?",[12,733,734],{},"Argon2id won the Password Hashing Competition in 2015 and is now recommended by NIST SP 800-63B for all new password storage implementations. Unlike bcrypt (4KB memory footprint) and scrypt, Argon2id's configurable memory parameter — typically 64MB or higher — makes it memory-hard. GPUs excel at parallel computation but have limited memory bandwidth per core. Memory-hard algorithms exploit this weakness: an attacker running Argon2id on a GPU sees dramatically lower throughput than an equivalent CPU, reducing the attacker's cost advantage. At 64MB\u002F64-thread configuration, expect ~15,000 hashes\u002Fsec on an RTX 4090 — vs 23 billion for SHA-256.",[710,736,738],{"id":737},"should-i-use-bcrypt-or-argon2id-for-a-new-project-in-2026","Should I use bcrypt or Argon2id for a new project in 2026?",[12,740,741],{},"If you're starting fresh: Argon2id. It has stronger theoretical guarantees, is memory-hard, and is NIST-recommended. The main reason to choose bcrypt is ecosystem maturity — it's been battle-tested for 25+ years and is available in every language's standard security library. If your stack's Argon2id bindings are immature or not actively maintained, bcrypt at cost 13+ is the pragmatic safe choice. Scrypt occupies a reasonable middle ground but has fewer audited implementations.",[710,743,745],{"id":744},"what-cost-factor-should-i-use-for-bcrypt-in-2026","What cost factor should I use for bcrypt in 2026?",[12,747,748],{},"Target a cost factor where hashing takes 100–300ms on your production server hardware. On modern cloud instances (equivalent to ~4-8 vCPUs), bcrypt cost 13 typically lands in that range. Test it. As hardware improves, bump the cost factor and use login-time rehashing to migrate existing hashes transparently. Never hard-code a cost factor without a mechanism to increase it — that's technical debt with a security expiration date.",[710,750,752],{"id":751},"what-is-a-pepper-and-do-i-need-one","What is a pepper and do I need one?",[12,754,755],{},"A pepper is a secret value — typically a 32-byte random string — stored outside your database, usually in an environment variable or a secrets manager like Vault or AWS Secrets Manager. Before hashing, you append (or HMAC) the pepper with the password:",[50,757,760],{"className":758,"code":759,"language":55},[53],"hash = Argon2id(password || pepper)\n",[37,761,759],{"__ignoreMap":58},[12,763,764],{},"Unlike a salt, the pepper is the same for all users and is never stored in the database. Its threat model is specific: it protects against a database-only breach. If an attacker steals your user table but not your server config, they have a pile of Argon2id hashes they cannot crack — because they're missing the pepper that was input to every single one of them.",[12,766,767,770],{},[45,768,769],{},"Do you need it?"," If your password hashes and your server secrets live in completely separate systems with separate access controls, yes — it adds a meaningful independent layer. If your database and your env vars are compromised together (a full server breach), a pepper provides no additional protection. It's not a substitute for a strong KDF, but used alongside Argon2id it's a low-cost, high-value defense-in-depth measure. NIST SP 800-132 calls this a \"secret value added to the password before processing.\"",[772,773,774],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":58,"searchDepth":538,"depth":538,"links":776},[777,778,779,780,781,782,783,784,785,786],{"id":21,"depth":538,"text":22},{"id":31,"depth":538,"text":32},{"id":77,"depth":538,"text":78},{"id":240,"depth":538,"text":241},{"id":342,"depth":538,"text":343},{"id":397,"depth":538,"text":398},{"id":427,"depth":538,"text":428},{"id":512,"depth":538,"text":513},{"id":662,"depth":538,"text":663},{"id":707,"depth":538,"text":708,"children":787},[788,789,790,791,792,793],{"id":712,"depth":545,"text":713},{"id":723,"depth":545,"text":724},{"id":730,"depth":545,"text":731},{"id":737,"depth":545,"text":738},{"id":744,"depth":545,"text":745},{"id":751,"depth":545,"text":752},"Security","Learn how password hashing and salting actually work — with GPU benchmarks, algorithm comparisons, and code examples. The definitive guide for backend devs.","md",[798,800,802,805],{"question":713,"answer":799},"Hashing is a one-way function that converts a password to a fixed-length digest. Salting adds a unique random value to each password before hashing, preventing rainbow table attacks and ensuring identical passwords produce different hashes even across millions of users.",{"question":724,"answer":801},"No. SHA-256 is designed to be fast — an RTX 4090 computes ~23 billion SHA-256 hashes per second. That speed is catastrophic for passwords. Use bcrypt, scrypt, or Argon2id, which are purpose-built to be slow.",{"question":803,"answer":804},"What is Argon2id and why is it recommended in 2026?","Argon2id is the 2015 Password Hashing Competition winner and NIST SP 800-63B's current recommendation. It's memory-hard, making GPU and ASIC attacks prohibitively expensive. At 64 MB memory cost, it processes roughly 15,000 hashes per second on an RTX 4090.",{"question":752,"answer":806},"A pepper is a secret value stored outside the database (e.g. an environment variable) and appended to every password before hashing. Unlike a salt, it's shared across all users. If your database is stolen but your server config is not, an attacker can't crack hashes without the pepper — it's a second independent layer of protection.","\u002Fimages\u002Fblog\u002Fsalting-and-hashing-developers-guide\u002Fhero.webp",{},"\u002Fen\u002Fsalting-and-hashing-developers-guide","2026-05-12",{"title":5,"description":795},"en\u002Fsalting-and-hashing-developers-guide",[814,815,185,816,817,818,819],"password hashing","salting","argon2id","sha-256","developer security","kdf","KBC65t97wvArhqrE3dc5HQI8VaFKy7bkBKDozcNFP2M",1782717487699]