Hashing Password on Client Side to avoid lag on single-core game server?

I am assuming that some of my users will be using the same password as they are for other sites.

In the context of protecting password from use on other sites (besides your own), it does not matter whether you hash the password client-side or server-side.

However, depending on your client environment, (i.e. web browsers) you may have difficulty finding a sufficiently well-optimized (strong) implementation of a Slow Password Hash. (e.g. Argon2 or BCrypt) So, your suggestion of client-side hashing may only be practical in App environments where native cryptography is available.

Of course, hashing client-side will do nothing to protect the password from being used on your site. An attacker who has stolen the hash can simply skip the client-side hash routine and become authenticated with your server. (never trust the client) Fortunately, this attack vector can be solved server-side. (see below)

The server has a constant game loop that needs to run, and I don't want the game to lag every time someone registers an account or tries to login. This approach would take some of the load off the server.

As you know, it is traditionally recommended to hash sever-side. If you are concerned about load, I submit a few suggestions:

  1. The Work Factor should be tuned only to take ~100ms on your target hardware. (your server) So, that's not really much of a lag, is it? I normally advocate 50ms-500ms, but your site could use a lower value and still be considered robust.

  2. The Hash should be in a separate thread from your main game loop (async) so that the game can continue to operate (albeit with slightly poorer performance) while the hash is calculating.

  3. You should include some form of DoS protection to limit the amount of hashes your server would calculate. Typically, the site would throttle sign-in attempts (per user?) so that an attacker would not be able brute-force effectively. Such a throttle would prevent endless hash computation.

  4. If you like, you could hash the password on both the Client and the Server, if the client environment has sufficient native support. (described above) Then you can get away with an even smaller work factor server-side.

    If the client-side has a sufficiently strong Work Factor, then it actually becomes reasonable to run only a quick (i.e. zero lag) SHA-2 on the server-side, and that will solve your problem completely. (thanks @HenningMakholm)

Finally, make sure the client sends the password (or hash) over HTTPS only. This is the most important thing, as no amount of supposed hashing will protect passwords typed into an HTTP site.


If the goal is only to protect the users from password-reuse when your database is compromised, then yes, you can move the hashing to the client-side.

You need to accept that this is insufficient in general for actually securing your application. It is insufficient because, in general, most such approaches have one of two faults:

  1. They transmit a token which is "password-like," in the sense that if I do break your assumptions about HTTPS or whatever, I get to log in as you.

  2. They store a token which is "password-like," in the sense that if I do get to look at a row in the users table, I get to log in as that user.

The first one is just any client-side manipulation which ends with "then I transmit to the server, the server hashes it, and the server compares if the hash is equal to something in the database." The second one is characteristic of challenge-response schemes, "I send you this string, you encrypt it with your key, you send back the encrypted version, and I see if I can decrypt it to get the original challenge again."

I chose the words for that second case very carefully because it reveals that actually, you can avoid these two if your system is based on asymmetric crypto, public keys and private keys. Your server can store a public key if your client can compute a private key. This used to be completely infeasible with RSA etc. ("Oh, I'll just use the password as a seed for a deterministic random number generator and do key setup each time" -- are you joking?) but has become more possible with elliptic curve cryptography, where the private key can often literally just be an arbitrary bitstring of a certain length. You use a key-derivation function to get that, and you're safe.

However, you might not want to "roll your own" crypto in any case: in that case, look for an implementation of the Secure Remote Password (SRP) protocol created at Stanford; it has seen a lot of cryptanalysis and has been around for a while and likely has implementations that you can find and use.


You can do the expensive part of password hashing on the client side, but you need to exercise care. For example, the following protocol would be wrong:

  1. Client establishes an authenticated, confidential channel to the server. (E.g., an SSL connection with a valid server certificate.)
  2. Client sends username to server
  3. Server looks up the corresponding password entry (username, salt, hashed_pwd)
  4. Server sends salt to the client.
  5. Client hashes their password with a costly password hash: client_hash = pwhash(salt, pwd)
  6. Client sends client_hash to the server.
  7. Server compares client_hash to hashed_pwd; the user is authenticated if they are equal.

That protocol would be insecure because the hashes that the server stores would be "password-equivalent"; a hacker that stole the server's password database would be able to replay the hashes over the protocol to impersonate any users. So the server still needs to do salted hashing; what it can do is move the costliest computations to the client so that the server only needs to compute a fast salted hash.

A better protocol would be this:

  1. Client establishes an authenticated, confidential channel to the server. (E.g., an SSL connection with a valid server certificate.)
  2. Client sends username to server
  3. Server looks up the corresponding password entry (username, client_salt, server_salt, hashed_pwd). (This is a changed step compared to the bad protocol: the good protocol has two salts, one for the client to use, one for the server.)
  4. Server sends client_salt to the client.
  5. Client hashes their password with a costly password hash: client_hash = pwhash(client_salt, pwd)
  6. Client sends client_hash to the server.
  7. Server computes server_hash = fast_salted_hash(server_salt, client_hash). The fast_salted_hash function could be HMAC or Blake2, keyed with the server_salt. (This is an additional step that did not exist in the bad protocol.)
  8. Server compares server_hash to hashed_pwd; the user is authenticated if they are equal.

By salting the password hash at the client side, it means that users who choose the same password will send different hashes over the wire, so that a hacker who compromises one client and learns its client_hash cannot replay it to log in as a user with the same password.

By only storing a salted hash of the client_hash, we defend against attackers who steal the password database, just like in conventional password hashing.