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?