6

There is an older game that solely uses UDP to communicate, and I wanted to add password authentication to the game to facilitate things like experience points and rankings. To this end, I decided to go with an implementation of SRP-6a communicated over UDP using an extension of the existing game protocol, and I ended up using cocagne/csrp on the game client side, tunneled through the game server, and mozilla/node-srp on the authentication server side, slightly modified as to produce the csrp-compatible HAMK follow-up response.

It has worked reasonably well so far, however I am not a fan of how the verifier is stored at rest, as it appears that in the process of calculating x the inputs are only hashed once before turning into the verifier v, see here. I have suggested that we change this library function to suit our needs (since it's reasonably small and already in-tree) and use something along the lines of PBKDF2. However I am getting (understandable) push-back from another developer about touching code of this nature, good intentions and all that.

It was suggested instead that perhaps the user's password itself undergo PBKDF2 hashing before being passed in as a password to the verifier creation and user challenge functions. That way, we do not have to modify any code in the csrp library itself. Is this a valid approach? Or is there another bear-trap waiting to snare us if we do this?

For what it's worth, the complete protocol document is here, so if I'm making any other sorts of snafus it'd be nice to know.

AlexMax
  • 163
  • 3

1 Answers1

5

The password in SRP is actually a shared secret of (possibly) low entropy. It can be the "password" as the human user understands it, or anything that is deterministically derived from the password. In your case, yes, using a password hashing function such as PBKDF2 is a valid approach. It has the following caveats:

  • PBKDF2, like bcrypt and other good password hashing functions, requires a salt. The salt is very important. Without the salt, the attacker could optimize attacks to a large extent; namely, he would have to pay for the PBKDF2 step for only one user (if he has several stored hashes for several users to crack). The point here is that the client and the server must use the same salt for a given user, so the client must know that salt somehow. But the salt must still be specific to each user (and changed whenever the user changes his password as well), so you cannot simply hardcode it in the client code.

    So you need to store the per-user salt on the server, and to convey it to the client as an initial authentication step. In SRP, the client first sends a message containing the user name (I) and a value that does not depend on the password (A). The server responds with the user-specific salt (s) and another message (B). You should send also in that server-to-client message the extra salt needed for PBKDF2.

    Alternatively, you may replace the hashing step in SRP with PBKDF2, but this involves modifying the protocol and the implementation libraries, which is probably not a good idea. It is simpler and altogether safer to add an extra PBKDF2 step.

    It is tempting to reuse the same salt as the one specified by SRP (the one called s in the protocol specification). Though this seems cryptographically safe here (the two salt usages being somewhat "separate" with all the PBKDF2 between them), this sort of things is known to be a matter of subtlety, and, there again, having an extra, independent salt is the safe, cautious method.

  • PBKDF2 employs many iterations in order to slow down attackers. But it slows down normal users as well, so you cannot crank up the iteration count as high as you wish. You should set up as high as you can tolerate, given the load and computational power of the involved computers, and the limits of the user's patience.

    In the case of integration with SRP, as with any other PAKE protocol, the brunt cost of password hashing is on the client side. So the appropriate iteration count depends on the client computer. If you have multiple clients then you must adjust to the least powerful computer that is still supposed to be able to connect. This point is especially crucial to Web-based client, with JavaScript, because JavaScript is very feeble for raw computations, severely limiting the number of iterations that you can tolerate. In your case, the client's code uses a C library, so you can have some client-side muscle, but since you are working with an "older game", you must consider that the client may be an "older machine", possibly an "elder machine". There are some perverts out there that still nurse some i486 computers.

  • You may consider using bcrypt instead of PBKDF2; bcrypt is arguably stronger against attackers with GPU.

Tom Leek
  • 170,038
  • 29
  • 342
  • 480