2

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.

ispiro
  • 2,085
  • 2
  • 18
  • 29

2 Answers2

5

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.


In addition, usernames are broadly predictable just like passwords are (or perhaps more so!). Notably, an attacker can likely guess a bunch of your usernames even before they ever see your password database. Which means that username salts are to some extent subject to precomputation attacks—if your scheme was broadly adopted, an attacker could conceivably precompute a rainbow table based on pairs of:

  • Common passwords (as is already well-known practice);
  • 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.

Luis Casillas
  • 14,703
  • 2
  • 33
  • 53
2

A salt must be unique not only for your service, but for all services.

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}$.

cypherfox
  • 1,442
  • 8
  • 16