TL;DR
You can calculate password hashes directly in a user’s browser using JavaScript and Web Crypto API. This improves security by preventing passwords from being sent to your server in plain text, even if compromised during transmission. However, it’s crucial to understand the limitations – browser-based hashing isn’t a replacement for robust server-side security practices.
How to Calculate Password Hashes in the Browser
- Choose a Hashing Algorithm: Select a secure hashing algorithm. SubtleCrypto supports algorithms like SHA-256, SHA-384, and SHA-512. SHA-256 is generally a good starting point.
const algorithm = { name: 'SHA-256' }; - Fetch the Crypto API: Access the Web Crypto API using
window.crypto.subtle.const crypto = window.crypto.subtle; - Convert Password to ArrayBuffer: Passwords are strings, but hashing algorithms require an
ArrayBufferinput. UseTextEncoderto convert the password string into a UTF-8 encodedUint8Arrayand then create anArrayBuffer.async function hashPassword(password) { const encoder = new TextEncoder(); const data = encoder.encode(password); const buffer = await crypto.digest('SHA-256', data); return Array.from(new Uint8Array(buffer)); } - Generate a Salt (Important!): Salts are random values added to passwords before hashing, making rainbow table attacks much harder. Generate a unique salt for each password.
async function generateSalt() { const array = new Uint8Array(16); window.crypto.getRandomValues(array); return Array.from(array); } - Combine Password and Salt: Concatenate the salt with the password before hashing.
async function hashWithSalt(password, salt) { const encoder = new TextEncoder(); const combinedData = encoder.encode(salt.join('') + password); const buffer = await crypto.digest('SHA-256', combinedData); return Array.from(new Uint8Array(buffer)); } - Hash the Combined Data: Use
crypto.digest()to hash the combined password and salt.const hashed = await crypto.digest('SHA-256', combinedData); - Convert Hash to Hex String: The resulting hash is an
ArrayBuffer. Convert it into a more readable hexadecimal string for storage.function arrayBufferToHexString(buffer) { return Array.from(new Uint8Array(buffer)) .map(x => ('00' + x.toString(16)).slice(-2)) .join(''); } - Store Salt and Hash: Store both the salt *and* the hash in your database. Never store passwords directly.
- Verification Process: When a user logs in, retrieve the stored salt for that user, combine it with the entered password, hash the combination using the same algorithm, and compare the resulting hash to the stored hash.
Important Considerations
- Key Stretching: For better security, use key stretching algorithms like PBKDF2 or Argon2. These algorithms repeatedly hash the password with the salt, making brute-force attacks more difficult. The Web Crypto API doesn’t directly support these; you may need a library.
- Browser Compatibility: Ensure compatibility across different browsers. Check Can I Use for browser support of the Web Crypto API.
- Server-Side Security is Still Essential: Browser-based hashing doesn’t replace server-side security measures. Always validate input, protect against SQL injection, and use secure communication protocols (HTTPS).
- XSS Protection: Protect your application from Cross-Site Scripting (XSS) attacks to prevent attackers from injecting malicious code that could steal passwords.