I have seen examples of password hashing that were: H(username + salt + password). What is the purpose of adding username? Is there any purpose?
No, there is no purpose. This is security theater*. The purpose of a salt is to make parallel attacks against all hashed passwords infeasible, and to break rainbow tables. Adding the username in there does not improve that behavior or increase any other aspects of security. It actually has a bit of a downside as you now will run into some troubles if you are changing the username, and you are required to maintain a more complex and non-standard system. In a purely cryptographic sense, there is no downside. Practically speaking though, more complexity means more bugs.
The properties of salts and usernames
You might think that the username itself may not be public, so it couldn't hurt to use it as an additional secret, but the fact is that the database will likely already contain the username in plaintext, invalidating this already questionable benefit. People should stick to pre-existing authentication techniques. But let's look at the properties of each of these objects:
A salt is:
Not secret - Salts are stored in plaintext.
Secure - They are generated randomly and are long.
Unique - Every user's salt is intentionally different.
A password is:
Secret - Assuming they are not put on a sticky note.
Unique - Ideally, at least, but not all passwords are ideal.
Now compare this to a username. A username is:
Not secret - They are public or at least stored in plaintext.
Not secure - No one thinks to choose a long and complex username.
Not unique - Usernames are safe to share between sites.
The traits of a good salt
Now, what exactly does a salt do? In general, a good salt provides three benefits:
It prevents an attacker from attacking every user's hash at once. The attacker is no longer able to hash a candidate password and test it against every single entry at once. They are forced to re-compute any given password to be tested for each user's hash. This benefit provided by a salt grows linearly as the number of distinct target hash entries grow. Of course, salts are still important even if only a single hash is in need of protection, as I explain below.
It makes rainbow tables infeasible. A rainbow table is a highly-optimized precomputed table that matches passwords to hashes. They take up less space than a gigantic lookup table, but take a lot of time to generate (a space-time trade-off). In order for rainbow tables to work, a given password must always resolve to the same hash. Salts break this assumption, making rainbow tables impractical as they would have to have a new entry for each possible salt.
It prevents targeted precomputation through rainbow tables, at least when the hash is truly random. An attacker can only begin the attack after they get their hands on the hashes (and with them, salts). If the salt is already public but the hash is not, then the attack can be optimized by generating a rainbow table for that specific salt. This is one reason why WPA2 is such an ugly protocol. The salt is the ESSID (network name), so someone can begin the attack for their target's router before they ever even get their hands on the 4-way handshake.
So what possible benefit would concatenating a value before hashing when this value is public, insecure, and re-used? It doesn't end up requiring the attacker dig for more information. It doesn't add to the security of a salt. It doesn't increase the complexity of the password. There is no benefit.
Proper password hashing
So what should they do to increase security? They can use a KDF such as PBKDF2, bcrypt, scrypt, or argon2 instead of a single hash. They can add a pepper, which is a random global value stored outside of the database and added to the password and salt, making it necessary to steal the pepper to attempt to attack the hashes rather than simply dump the database using SQLi.
EDIT: As some of the comments point out, there is one contrived scenario where the username would be beneficial to add into the mix. That scenario would be one where the implementation is broken badly enough that the salt is not actually a salt, and the username is the only unique or semi-unique per-user value in the database, in which case mixing in the username would be better than nothing. But really, if you have no salt, you should start using one instead of trying to use usernames. Use real security and don't be half-assed when your users' safety is on the line.
* In this context, I am defining security theater as the practice of implementing security measures that do not actually improve security in any meaningful way and are only present to provide the illusion of better security.
If the salt is sufficiently random as it is supposed to be then adding the username does not add additional protection. But it also does not cause any harm apart from making the code more complex which increases the likelihood of bugs. When doing a code review it might be taken as a sign that the developer does not fully understand what he is doing.
If instead the salt is just mostly static or derived from the password then the username might help since in this case the username essentially serves the purpose the salt failed to serve. But, this is not the recommended way since the salt is supposed to be random and a username is not.
See Is it a good idea to use the user's username as a salt when hashing a password like hash(username_str + password_str)? and What should be used as a salt? for a deeper discussion of what a good salt should be and why the username is not a good salt. See also Why are salted hashes more secure for password storage? to understand the purpose of the salt in the first place.
As @Damien_The_Unbeliever pointed out in a comment, it can prevent user impersonation in a scenario where the system has been partially compromised.
Imagine the following scenario. Somehow, an attacker has gotten read/write access to the db table used for login, containing usernames, password hashes, and salts -- perhaps via a SQL injection attack. This attacker has no other elevated access to the system, but wants to impersonate a user in the system in an untraceable (or hard to trace) way.
- First, the attacker records the original password hash and salt for the victim's account.
- Next, the attacker registers their own account, and copies the password hash and salt from their account into the victim's account.
- Now, the attacker can log in to the victim's account using the attacker's own password.
- When the attacker is done, they restore the victim's password hash and salt, making it difficult for the victim to realize what has happened.
While in some systems, an attacker with this level of access could simply generate a password hash using the victim's username, salt, and an arbitrary password (rather than copying their own), this is not possible if the system uses a pepper in addition to the salt (a globally-defined constant added to the input for all password hashes, which is stored someplace different from the password hashes and salts). Without compromising the pepper, the only way the attacker in this scenario can set a password hash with a known password for the victim is to copy one generated by the system, as described above.
Note that two-factor authentication likely can't even prevent this. If the attacker also has access to the 2FA initialization codes for users (perhaps stored in the same db table), the attacker's code can be temporarily written into the victim's account as well.
In contrast, if the username is used in calculation of the password hash, this particular impersonation attack will not work. Even once the attacker copies their password hash, salt, and 2FA code into the victim's account, using the attacker's password on the victim's account will not result in the same password hash as for the attacker's account, and so the login will fail.
It is debatable whether the benefits are worth the additional complexity and possibility of bugs of course, since this scenario requires the attacker to have already seriously compromised the system, and in this scenario the attacker doesn't recover the user's actual password. But, it can be argued that this is an additional protection.
For starters, password hashing should be done with a dedicated password hashing function such as bcrypt, Argon2, scrypt or PBKDF2. In that case, you don't have to deal with concatenating the salt and password like a primitive; the function takes those as separate arguments. See "How to securely hash passwords?", one of the top Q&A's on this site for this topic.
The purpose of the salt in password hashing is to randomize the hash function that's applied to each password entry. So three rules that generally apply to modern specialized password hashes are these:
- Everytime you enroll a new password you should generate a fresh salt. (Note this means that when a user changes their password, you should generate a fresh salt for that password, not reuse the salt for the old one. Salts are bound to states of a password database entry, not to users.)
- The salt's value should be independent of the password itself; knowledge of the password should be of no help in guessing the salt, or otherwise an attacker could use such knowledge to precompute an attack table. An obvious example here is you shouldn't use the password itself as the salt. (That's less dumb than it might sound—you wouldn't store the salt along the password in this case—but it is dumb, because two users with the same password would have the same hash.)
- The chance that two salts are equal to each other should be very low. Preferably not just within your application, but globally across the whole world.
Now we can answer your question by making these observations:
- A salt that's made up of a sufficient number of random bytes satisfies these criteria already. 16 random bytes (from a cryptographically strong random number generator) sounds sensible.
- Concatenating the username to such a random salt doesn't help.
- If the salts are being generated in some way that's predictable but somewhat nonrepeating—for example, as a counter that's incremented each time you enroll a new password, or as a timestamp—concatenating additional values like the username or your site's domain name can help make it more unique. But using random salts would be simpler (no need to keep track of a persistent counter state).