Authenticating PBKDF2 params from the server

In my app, I use PBKDF2 to stretch a user inputted password. I split the result in two: the first half is stored locally as an encryption key (and never sent to the server), and the second half is sent to the server as a regular "password".

Rather than hardcoding a fixed cost for PBKDF2 on the client side, I allow the client to choose a cost, then send it to the server with user registration for storage. Then, when a user wants to sign in, a query is made to the server to retrieve the public parameters, which includes the password cost and salt.

I've discovered however that if the server is hijacked, it can send a cost back to the client of 0 or 1, and trick the client to returning an easy password.

What I need to do is authenticate the parameters returned and make sure they are valid. The way to do that is to generate an auth_hash by taking the HMAC of the cost and salt with an auth key derived from the user's input password.

The problem is that I already have existing users. This solution would be easy to do for new users, but I'm unsure how to transition existing users to this system. Existing users will not have an auth_hash associated with their account. They will need to upload one on their next sign in. Which means, I will have to allow a single sign in, which means trusting the parameters from the server. And even if the user did actually have an auth_hash, the server can send back a nil auth_hash to trick the client into thinking the user doesn't have an auth_hash and is permitted a one time sign in to upload one.

You can see it's quite a tricky problem.

One possible solution is to hardcode a minimum cost of 3,000 on all clients. This way a server couldn't get away with sending a 0 or 1 cost. The question with this is: if a user's actual cost is 10,000, but the server deceptively sends back 3,000, does this leak information? Recall that only the second half of the PBKDF2 output is sent to the server, not the first half. Obviously if the user's actual cost were 10,000 and the client instead received 3,000, and I sent back the beginning of the result, that would be no good. But do I gain any advantage here by only sending the second half of the output?

• You can also enforce a minimum strength. – eckes Jun 21 '17 at 19:57
• What do you mean a minimum strength? – Snowman Jun 21 '17 at 20:58
• Well, if the client does not accept iteration count and salt length smaller than a sane value (like 5000 iterations and 8 bytes) it will be enough to reliable drive some integrity protection bytes or avoid leakage. – eckes Jun 21 '17 at 21:00
• So setting a minimum on the iteration count and salt length? But even at a 5000 minimum, this is degrading the security since the user expected protection of say 50,000 iterations. – Snowman Jun 21 '17 at 22:15
• Don't know the overall solution. If you verify the local result (encryption key) against actual encrypted data it won't match if the server cheats, then you also can skip sending anything to the server. – eckes Jun 22 '17 at 8:33