20

I read that a password and a salt needs to be combined and then hashed. You save the result and the salt in plaintext. Is it a good practice to use the username as a salt? Why and why not?

I also read that good practice is that you need to add a nonce to the password-salt-hash and hash that too. This is done so no replay attacks can be carried out.

The function looks like this:

hash(hash(password+salt)+nounce)

7
  • 24
    Don't roll your own code for this. The framework you are using probably has built-in functions for hashing the password and verifying the password. For example, in PHP there are the password_hash and password_verify functions.
    – mti2935
    Commented Sep 2 at 11:23
  • 8
  • 1
    "I read ..." where? Context will be important for any advice. "Good practice" is ot use a tried and tested password hashing library that has all this figured out and implemented for you.
    – schroeder
    Commented Sep 2 at 11:42
  • 5
    Is it a good practice to use the username as a salt No, because then an attacker would know exactly what to use as the salt for each user. Commented Sep 2 at 21:39
  • 8
    @JohnGordon: The attacker knows that in any case, because the salt isn't secret. As already pointed out by Gh0stFish, the salt only has to be globally unique (and unpredictable to prevent precalculations). Typical usernames are certainly neither.
    – Ja1024
    Commented Sep 2 at 23:25

4 Answers 4

64

Hashing the salt doesn't really help, because the point of a salt is to be unique, not secret. Using usernames as a salt isn't great either, because the salts should be globally unique, and usernames are often re-used between sites.


But the fact that you're even thinking about what the salt should be is a bad sign, because proper password hashing algorithms manage all that for you. And any attempt to roll your own crypto or hashing is a really bad idea, because there are a lot of subtle things that can go wrong.

The OWASP Password Storage Cheat Sheet has a lot of good recommendations and guidance on what the current best practices are - so I would start by reading that.

6
  • 1
    I would like to add that a salt should primarily be random (and thus most likely unique), to best mitigate rainbow table attacks with precalculated hashes. It's mentioned in the OWASP link.
    – Marcel
    Commented Sep 3 at 13:02
  • 1
    @Marcel as long as the salt is unique then it doesn't matter whether it's random - something like a type 1 UUID or even a site specific string + sequential integer is perfectly fine. But if you find yourself thinking about how to manually generate salts yourself then you're probably doing it wrong, because that should be handled for you by the hashing library.
    – Gh0stFish
    Commented Sep 3 at 13:12
  • 3
    @Gh0stFish: Uniqueness isn't enough. If the salts are predictable, then an attacker can create a lookup table for one or multiple users ahead of time, giving them the chance to attack short-lived passwords. For example, imagine a user changes the password every month, but a brute-force attack takes more than a month. With purely random salts, the attack isn’t doable. However, if the salts are predictable, then the attacker can start before the user is even registered and might then have enough time to successfully brute-force the password while it’s still valid. So Marcel is correct.
    – Ja1024
    Commented Sep 3 at 14:54
  • 1
    Which all up to date crypto libraries seem to do. (Look at reported vulnerabilities of any crypto library you use, as long as teh vulnerabilities require nation state resources or greater, it's safe to use.).
    – Questor
    Commented Sep 3 at 20:37
  • 1
    All these back and forth are basically solved by using well-known hash package on your platform and not roll your own. There would be some fairly standardized recommendation for salt sources per package deployment instructions.
    – Nelson
    Commented Sep 4 at 1:46
10

I also read that good practice is that you need to add a nonce to the password-salt-hash and hash that too. This is done so no replay attacks can be carried out.

This seems to be about a login exchange, not how it's saved in the database. When logging in, the server sends a nonce to the client. The client hashes the password with the nonce and sends it to the server. The server can verify the password. This message cannot be replayed, because then the server sees the nonce for the second time. Authentication to a MySQL server works this way.

This mechanism is not secure. The easiest way to make it secure is to use an authenticated TLS connection. That also solves the replay vulnerability, so this nonce challenge mechnism is no longer needed.

5
  • The mechanism in the OP has an advantage that simple TLS doesn't: it protects against malicious servers. A bare password is never transmitted to the server during login, so even if an attacker compromises the server, they can't get the password.
    – Mark
    Commented Sep 2 at 19:53
  • 1
    @Mark: In the proposed scheme, the password hash effectively is the password. Knowing it suffices to authenticate towards the server. Since the scheme forces the server to either store the password itself or the password hash, they always end up storing password-equivalent data as plaintext – which is obviously the worst you can do. The only positive attribute of the scheme is that it protects other applications in case of password reuse. But of course nobody should reuse passwords in the first place, so this isn’t really a strong argument for the scheme.
    – Ja1024
    Commented Sep 2 at 20:20
  • 1
    @Mark: There are algorithms which do avoid both sharing the password with the server and storing password-equivalent data on the server (see Augmented PAKE). But this is a difficult cryptographic problem which requires a lot of expertise and peer reviews to get right. Mixing a bunch of hashes isn’t enough.
    – Ja1024
    Commented Sep 2 at 20:27
  • @Ja1024, if someone's compromised the server to the point of being able to steal in-transit encrypted data, it's likely that they can do anything as anyone without needing to authenticate. The whole point here is to contain the damage to just the compromised server.
    – Mark
    Commented Sep 2 at 20:50
  • @Mark: This isn’t about in-transit data or a complete compromise of the server. With the OP’s scheme, the attacker can, for example, try a plain old SQL injection attack to grab the password hashes and then use those hashes to authenticate. It’s like storing passwords as plaintext – the only difference is that the password is derived from some user-provided secret.
    – Ja1024
    Commented Sep 2 at 20:59
9

The safest way is to not store the password at all. Where possible use standards like OIDC or OAuth 2.0 to avoid the need for storing a user's password.

If you really need to store passwords, use a password specific hashing function such as Argon2id. Password specific hashing functions will take care of salting for you, are deliberately slow to limit offline attacks, with the degree of slowness being configurable.

4
  • I don't believe there's anything inherently insecure about password logins - if done right, of course! But I do believe that the positives of offering password logins far outweigh the negatives. Offering only OAuth2/OIDC causes issues if the authorization server decides to lock the user out, introduces a single point of failure, comes with privacy issues, and possibly makes it hard to switch auth providers.
    – Marko Zajc
    Commented Sep 4 at 13:51
  • 1
    @MarkoZajc "if done right" is a big IF. And even when 'done right', they inherently require passing a secret which creates many opportunities for leakage.
    – JimmyJames
    Commented Sep 4 at 15:54
  • +1 but on a (perhaps pedantic) side note, hashing is not storing the password. That is, the password hash is not the password.
    – JimmyJames
    Commented Sep 4 at 15:56
  • @MarkoZajc - I think you've misunderstood; I'm not arguing against the use of passwords as an authentication mechanism (although I could do), just pointing out the benefits of delegating authentication (probably still with passwords) via OAuth / OIDC. I'm also not necessarily talking about federating with social ID providers which your comment on privacy seems to imply.
    – andycaine
    Commented Sep 4 at 16:11
4

This is not secure.

First off, as Sjoerd already pointed out, your question title about storing a password hash is detached from the question text which deals with a password-based authentication scheme. Those are two entirely different aspects.

The authentication scheme you've proposed is not secure for many reasons. It actually seems to be a reinvention of the old Digest Access Authentication scheme and has many of the same problems.

  • The server has to store the password or password-equivalent data as plaintext. Since the server must be able to calculate hash(hash(password + salt) + nonce) itself and then compare the result with the client-provided value, they either have to know password or hash(password + salt). In the first case, your server is keeping the plaintext passwords of all users, which is a security nightmare. In the second case, the passwords are at least hashed, but knowing the password hash alone is sufficient for an attacker to impersonate the user in your scheme. So with regards to your application, there's no improvement. You're merely doing your users a service by protecting the original passwords in case they've reused them somewhere else (which of course they shouldn't do in the first place).
  • In your scheme, calculating the password hash has to happen client-side. Not only does this make JavaScript a strict requirement for your application. It also means you have to find a production-ready JavaScript implementation of a strong password hashing algorithm, you cannot fully control the hash calculations, and you have to be careful not to overload weak client devices like smartphones or tablets – while at the same time making sure the password hashes are strong enough to withstand brute-force attacks.
  • If you use the username as the salt, then an attacker can reuse calculated hashes, potentially allowing them to perform a brute-force attack much more efficiently. For example, if the same username is present in a different application that uses the same hash algorithm, then it's possible to attack both applications at once – the attacker can guess a password, calculate the hash with the username as the salt and then compare the result with the stored hashes in both applications. If the username is very common, the attacker may also have a precomputed table of hashes which lets them search for a match with a simple lookup.
  • Since the entire scheme is home-grown, there's a huge risk of making other mistakes that drastically reduce security.

The proper approach is to transmit the password over a TLS-encrypted connection and use a strong password hash algorithm like Argon2 on the server to calculate the stored hash. While Argon2 is state of the art, it's also fairly new and not yet implemented in the standard libraries of all major programming languages. The are alternatives like bcrypt, though.

Argon2 has different parameters which let you fine-tune the strength of the hashes. You can find recommended values in RFC 9106. Note that Argon2 has built-in support for salts, so you just need to make sure you're proving a sufficiently long random value. You should use at least 128 bits obtained from a secure random number generator (most programming languages have a built-in feature for this).

Another interesting feature of Argon2 is that you can mix in a secret value (through the parameter K). This way, an attacker can only perform a brute-force attack if they've obtained both the hash and the secret K.

3
  • "The proper approach is to transmit the password..." let me stop you there. I'm not keen on sending my password over any connection. The server can store a salted hash and let me know what salt to hash my password with, as long as the salt is unique between servers.
    – Corey
    Commented Sep 2 at 23:15
  • 3
    @Corey: If sharing the password with the server is a major problem, that’s a bad sign, because it means you’re either reusing your password for different services, or the password has a specific pattern which reveals information about other passwords. Neither should be the case. For each service, you should generate a purely random password which is useless outside of the service. If you do that, then all the client-side hashing gymnastics become unnecessary. There's a reason why even large, professionally managed applications do transmit the password over TLS: It's a reasonable, secure choice
    – Ja1024
    Commented Sep 2 at 23:38
  • 2
    @Corey: The problem with the OP’s scheme is that it exchanges a small benefit (protection of reused passwords) for a huge disadvantage (the server has to store password-equivalent data as plaintext). There are algorithms which can solve this dilemma (Augmented PAKE protocols like OPAQUE), but those are complex and not widely supported yet.
    – Ja1024
    Commented Sep 2 at 23:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .