9

I am currently implementing a simple login system. I would like to securely store all the user passwords in my database. The passwords are the most important components. All the other user data is not important. If an account gets hacked, as long as the password cannot be figured out its fine; other data are things like: score and account creation date.

I am assuming that some of my users will be using the same password as they are for other sites. So i need to make sure that the password cannot be read by a potential hacker.

Can I generate the Hash on the client side somehow to avoid any hacker ever seeing the real password?

Before you say I shouldn't do it on the client. 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.

EDIT: Since this question was closed I would like to elaborate. Moving to the client allows me to use a lot more rounds when hashing the password, since this wont affect the server or other users experience in any way. This also makes it much easier to prevent a user from spamming requests and slowing down the server.

  • 14
    If login requests cause your game loop to lag, then you should handle those on a separate thread. Hashing on the client side is generally not a good idea. See: http://security.stackexchange.com/q/8596/29865 – Ajedi32 Jan 17 '17 at 14:22
  • I am limited to a single core server. And like I have said, if an account gets hacked that doesn't matter. The only thing tht matters is if a password can be turned into plain-text because it can be used on other sites like: paypal or facebook. –  Jan 17 '17 at 14:28
  • 3
    You would still need to hash on the server side if you hashed on the client - else you'll have a bunch of password equivalent strings sitting in your database, which, if stolen, could probably be brute-forced anyway to reveal the password (since you can't control the client specifications, you'd probably have to err on the side of a relatively quick hash). If timing is critical, you could have an authentication server, which is distinct from the game server - yes, it increases cost, but allows for easier scaling later... – Matthew Jan 17 '17 at 14:44
  • Why are you "limited to a single core server"? That's one of the first things I'd do something about. – Michael Hampton Jan 17 '17 at 23:20
  • @SidneydeVries Simply put if your game has SO much logging in going on that your server can't keep up with it, then it should also be generating enough income to pay for a server that can keep up with the load. Either that or you've got a terrible business model. – corsiKa Jan 18 '17 at 03:31
  • 3
    Just a note, you can still do multithreading on a single core. It won't necessarily be faster, but the benefit is it likely won't block for the entire duration of the hash computation. Rather it would probably switch threads *during* the hash computation depending on how the code is implemented (what language) and how long it takes (for example hundreds of ms with bcrypt). – Steve Jan 18 '17 at 04:03
  • From a software design POV, unless you have a specific reason for writing your own security layer I'd say focus on writing your game and delegate the responsibility of asserting identities to a specialized service: Use Oauth2 + a couple of public providers like Google or Facebook. – OnoSendai Jan 18 '17 at 14:41
  • 1
    I'm voting to re-open this question, because unlike the linked duplicate, this question is specifically asking in relation to server load issues / server relief. – 700 Software Jan 18 '17 at 15:06
  • Yeah thats the main reason i want to move it to the client. –  Jan 19 '17 at 02:11

3 Answers3

19

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.

700 Software
  • 13,807
  • 3
  • 52
  • 82
  • 2
    100 ms is a lot in the context of a game I think. Also is HTTPS a must if I already hashed the password before I send it? Why is that the case since it would already be hashed –  Jan 17 '17 at 14:35
  • 3
    In a web browser environment, **HTTP tampering can be used to disable all client-side hashing.** Even if it weren't, theft of a hash is pretty serious, especially if it was for a sensitive service (e.g. Facebook, Google, PayPal, Online Banking) as you suggest is possible. – 700 Software Jan 17 '17 at 14:39
  • 2
    As for lag, I think that #2 (async) is your best option, but you can also tune the work factor to be lower as suggested in #1, and consider #4 for additional security. – 700 Software Jan 17 '17 at 14:42
  • 2
    Depending on the technology you use, you might be able to set (operating system) priorities for your different threads. You would give the "login" thread a low and the "game" thread a high priority. Then the operating system will make sure that if load goes high, the game keeps running smoothly and the login has to wait a bit (which ought to be fine). – 5gon12eder Jan 17 '17 at 17:17
  • 3
    For option 4 it would be secure to reduce the server work to a single SHA-256 (or even MD5 if you're really desperate for CPU cycles), right? – hmakholm left over Monica Jan 17 '17 at 20:43
  • 1
    How about having a separate server to manage usernames and passwords which, in response to a valid login request, issues a token that contains the username, timestamp, and a hash of those things, all encrypted using a secret that's shared with the game server? The game server could quickly validate tokens, but nothing in the tokens would say *anything* about the users' passwords. – supercat Jan 17 '17 at 21:11
  • 1
    @HenningMakholm That's actually a good point. The stronger, more expensive client-side hash should output a relatively long fixed-length pseudo-random string. Even a single round of SHA-256 on the server should be more than strong enough to resist a brute-force attack against the server-side hash of _that_ string. – Ajedi32 Jan 17 '17 at 21:23
  • 1
    @Sidney 100ms is **not** a lot in the context of a *network operation before the game has even started*. You should be issuing clients an auth token (1-to-m) after successfully authenticating, which is just a database check on subsequent requests. – Riking Jan 17 '17 at 21:32
  • 1
    "I cannot think of any reason *not* to hash on the server." There's a common one right there in the question! I think this answer dodges a quite reasonable question: how to shift the cost of password hashing from the server to the clients? It's not a novel question either: several submissions to the [Password Hashing Competition](https://password-hashing.net/), including [the winner Argon2](https://password-hashing.net/submissions/specs/Argon-v3.pdf) support "server relief." Once we acknowledge that the server should throttle login attempts in any case, using server relief is no less secure. – Luis Casillas Jan 18 '17 at 00:44
4

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.

CR Drost
  • 221
  • 1
  • 6
2

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.

Luis Casillas
  • 10,181
  • 2
  • 27
  • 42