TL;DR
Client-side hashing can significantly reduce the impact of denial-of-service (DoS) attacks exploiting slow hash functions. By pre-hashing data in the browser, you offload the computationally expensive hashing process from your server to the client’s machine. This limits the server’s workload and makes it harder for attackers to overwhelm it with requests requiring lengthy hash calculations.
How Slow Hash DDoS Attacks Work
Slow hash attacks target applications that use cryptographic hash functions (like SHA-256 or bcrypt) on user-supplied data. Attackers send specially crafted input designed to take a very long time to hash, consuming server resources with each request. This can lead to service degradation or complete outage.
Client-Side Hashing: A Solution
The core idea is to move the hashing operation from your server to the user’s browser before sending the data. Here’s how:
- Choose a Suitable Hash Function: Select a hash function that is readily available in JavaScript (e.g., SHA-256). Avoid functions requiring native extensions if possible, to maximise compatibility.
- Implement Client-Side Hashing: Use JavaScript to hash the data before submitting it to your server. Libraries like
jsSHAor built-in Web APIs can help.const encoder = new TextEncoder(); const data = 'your input string'; const encodedData = encoder.encode(data); const hashBuffer = await crypto.subtle.digest('SHA-256', encodedData); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join(''); console.log(hashHex); // The hashed value - Send the Hash to Your Server: Instead of sending the original data, send only the generated hash to your server.
- Server-Side Verification: On the server, store and verify hashes instead of raw data. When a user submits a hash, compare it against the stored hash.
// Example in Python (Flask) from flask import Flask, request import hashlib app = Flask(__name__) @app.route('/verify', methods=['POST']) def verify(): submitted_hash = request.form['hash'] stored_hash = 'your stored hash' if submitted_hash == stored_hash: return 'Hash matches!' else: return 'Hash does not match.' if __name__ == '__main__': app.run(debug=True) - Salt the Hash: Always use a unique, randomly generated salt for each user to prevent rainbow table attacks and improve security.
// Example in JavaScript: const salt = 'your random salt'; const saltedData = salt + data; hashValue = SHA256(saltedData);
Benefits
- Reduced Server Load: The server no longer performs expensive hash calculations.
- DoS Mitigation: Attackers can’t easily overwhelm the server with slow hashing requests.
- Improved Response Times: Faster response times for legitimate users.
Considerations
- JavaScript Dependency: Requires JavaScript to be enabled in the user’s browser.
- Man-in-the-Middle Attacks: Protect against man-in-the-middle attacks using HTTPS (SSL/TLS). Ensure data integrity during transmission.
- Hash Function Security: Choose a strong and well-vetted hash function.
- Salt Management: Securely store salts associated with each user’s hash.