In this article, we’ll explore how a secret key used in a simple hash function can be recovered through brute-force techniques using JavaScript and Node.js. This is an educational example intended to demonstrate the importance of secure key handling and the risks of weak or guessable secret keys.
Scenario: Hash-Based Authentication
Imagine a game provider sends a string in the format:
<hash>-<username>-<gameId>
For example:
b636b-darklord19-poker001564
This string is used to authenticate a player joining a game. The hash is generated using a hash function that combines the username, gameId, and a secret key known only to the server.
How the Hash is Created
Let’s assume a very simple hash function, like this one:
jsCopyEditconst crypto = require('crypto');
function createHash(username, gameId, secret) {
const data = `${username}:${gameId}:${secret}`;
return crypto.createHash('sha1').update(data).digest('hex').substring(0, 5);
}
- This function concatenates the username, gameId, and secret with colons (
:), then hashes the result using SHA-1. - Only the first 5 characters of the resulting hash are used (to keep it short).
So for example, this could generate:
createHash('darklord19', 'poker001564', 'dragon')b636b
// returns something like ""
Then the full string becomes:
b636b-darklord19-poker001564
Now, suppose this is all you have. You want to find out the secret key used to generate the hash.
Brute-Forcing the Secret Key
In our simplified case, the secret key is short and guessable. We can brute-force it by testing every number from 0 to 999999 and checking if the hash matches:
const crypto = require('crypto');b636b
function createHash(username, gameId, secret) {
const data = `${username}:${gameId}:${secret}`;
return crypto.createHash('sha1').update(data).digest('hex').substring(0, 5);
}
// Known values
const username = 'darklord19';
const gameId = 'poker001564';
const targetHash = '';for (let i=0;i<999999;i++) {
const hash = createHash('darklord19', 'poker001564', i);
if (hash ===targetHash) {
console.log(`Found key! Secret = ${i}`);
break;
}
}
When this script runs, it will loop through every possible numeric key and compare the generated hash to the targetHash. Once it finds a match, it logs the correct secret and stops.
Found key! Secret = 20187
Even if the key we discover through brute-force isn’t the original secret used by the server, as long as it produces the same hash output, the result is indistinguishable. This means the server will accept it as valid, since the hash matches — effectively bypassing the need to know the exact original key.
To improve accuracy and increase confidence in the key we find, we can run our script against multiple authentication strings. This allows us to verify that the same key consistently produces matching hashes across different inputs, making it more likely to be the correct secret.
Lessons Learned
While this is a simplified example, it highlights a real-world vulnerability: if the hash used for verification is too short, it becomes much easier to brute-force or guess the correct value.