Salts should be unique so that an attacker can't brute force multiple passwords at once. However, since usernames are unique, wouldn't it be possible to use some representation of the username as a salt?

Of course that means that if a username changes, the password needs to be rehashed. I'm asking from a cryptographic perspective, not a user-experience perspective.

• – cypherfox Mar 12 '18 at 13:43

However, since usernames are unique [...]

But the same user is supposed to get a fresh salt when they change their password. Salts are not supposed to be bound to usernames as you suggest; they're bound to states of individual password database entries. If the attacker gets their hands on multiple password entries for the same user in your scenario, those entries will have the same salt and thus there is a work savings they can achieve.

One practical illustration of this is to consider that many password storage APIs have been carefully designed to avoid salt misuse by factoring the process into these two operations:

1. Generate password token: Take a password as input and produce a password verification token as output. This token incorporates:
• A description of the algorithm that was used and any parameters required to rerun it;
• A random salt (generated internally by the API operation);
• The raw password hash output.
2. Verify password: Takes a password and a verification token as input, and outputs true if and only if the password checks out against the token. Internally this:
• Decodes the token to determine the algorithm, parameters, salt and raw hash;
• Uses this information to select the password hashing algorithm and parameters;
• Hashes the supplied password with those choices;
• Compares the resulting raw hash to the one stored in the token.

When you use an API like this to manage your users' passwords, you call operation #1 not only when you enroll a new user, but also when an existing user changes their password. Which means a new salt is implicitly generated afresh, and you can't make the mistake of reusing the same salt for the same user.

• Usernames known to be common at large, or that have a high probability of being used among a small population of interest (e.g., a foreign adversary's influential politicians).

Which means that although password salts are nonsecret, they ideally should be unpredictable as well. That sounds like an oxymoron at first, but what it means is that an attacker should not be able to guess what a password entry's salt might be before they actually see the entry. Random salts trivially ace this test; usernames don't score well on that criterion, and neither do @cypherfox's "site identifiers" or counters, for that matter.

• Thanks. The second part makes sense to me. The first part, however, multiple passwords for the same user - but only the current password matters, and only the current one will even be in the database! – ispiro Mar 12 '18 at 20:25
• I'll link to this Q&A discussing case #1 from my comment. – Luis Casillas Mar 12 '18 at 20:39
• @LuisCasillas You must have skipped a word or two in my answer. The first $\text{salt}_1$ I recommend to use for client-side (where the more random salt is not visible to the adversary) and $\text{salt}_2$ on the server side with a randomized counter. This is already a random salt, except we count it up to avoid collisions and we bind it to the context of the user and service. – cypherfox Mar 13 '18 at 3:04
• @LuisCasillas Your scheme assumes the user sends their raw password to the server, which opens the user up to many more attacks than just snapshots, such as rogue server code (that cannot be observed by the users). Injected JavaScript does affect both schemes (but may be observed by users). – cypherfox Mar 13 '18 at 3:07

So you must introduce a service-salt too.

$$\text{salt} = H(\text{service-identifier} \| \text{username})$$

This is an absolute minimum. To defend against multiple server compromises, you may want to add a counter, which may be randomized when the user registers and incremented when they change the password.

Now we have:

$$\text{salt} = H(\text{service-identifier} \| \text{counter} \| \text{username})$$

I recommend the first case when you derive keys in the browser using Argon2i from the user's user + passphrase. When registering you may generate a suggested default password using 6 words from EFF' wordlist. The server can use the second salt with a quick $\text{HMAC}(\text{salt}_2, \text{argon2i_output})$ when storing the verification string.

You should only send passwords to the server when the user's browser does not support JavaScript (or if it is disabled), including a disclaimer that the form will send the password to the server and if the user does not want this, they should enable JavaScript.

Rogue JavaScript can obtain the user' credentials with or without this defence.

Additionally Argon2i enables the user to spend the computational cost when registering and logging in without imposing slow-hashing load on the server.

Edit: Q. Necessity of Randomness of Salts? includes more information and one note worth a mention here is that if the salts can be publicly computed, then an adversary can precompute many guesses before compromising the server. Whereas with the random salt, they cannot. This is safe with the hybrid solution I described where the user uses $\text{salt}_1$ and the server uses $\text{salt}_2$ with a randomized $\text{counter}$.

• service-salt - that's what I hinted at by writing "representation of the username". As for your second point - what do you mean by multiple server compromises? – ispiro Mar 12 '18 at 12:47
• @ispiro If you assume your service is never compromised you can store passwords in plaintext. But if you assume the server may be compromised once, then you can use the first salt. If you assume the server may be compromised more than once, then you'll need the second salt. – cypherfox Mar 12 '18 at 12:50
• Why did you suggest an HMAC? Did you mean to write "Hash"? – ispiro Mar 12 '18 at 12:50
• I meant HMAC(salt2, argon2i_output). – cypherfox Mar 12 '18 at 12:53
• Thanks for your explanation of the "multiple server compromises". Though I think if the server is compromised, the "representation" will be changed. Simply changing the service-identifier should suffice I think. – ispiro Mar 12 '18 at 12:53