Implementations of the passphrase

Do you manage a web service, a website, or software and want to implement password management for your user sessions?

Posted on
6 minutes
1143 words

First, read this article on what makes a good password, then continue reading this one.

Note: This post was translated from french with the help of AI. The original post was written with the knowledge of a younger me.

Password Rules

If you’re tempted to set password rules like “between 8 and 20 characters” or “3 uppercase letters,” stop right there.

This approach is outdated, overly rigid for users, and leads them to create passwords that are hard for them to remember but very easy for bots to crack.

Don’t believe me? Check out the Twitter account passwordistoostrong.

Example:

Imagine the rule:

“Between 8 and 20 characters, at least 3 uppercase letters, and a punctuation mark.”

This password would qualify: “AbCdEfgh!” even though it’s not actually strong.

Meanwhile, the user is left frustrated.

Entropy

Instead, use an entropy-based system.

The zxcvbn library (named after the bottom row of keys on a U.S. keyboard) is a great implementation for calculating password strength based on entropy. It’s available in a wide variety of programming languages.

The advantage is that there are no rigid rules for estimating password strength. The library calculates the complexity of your password (considering the rarity of characters, their patterns, etc.) and assigns it a score. You can then decide from which score threshold you consider the password sufficiently secure.

Of course, you must use the same scoring and thresholds on both the server and client sides. If they differ, the server has the final say since the client side can be tampered with by an attacker (it’s not secure).

Avoid Compromised Passwords

Once you’ve set a threshold to block weak passwords, you still need to check if the entered password hasn’t already been compromised.

This is an optional additional safeguard because if your complexity threshold is high enough, the way the password is stored can already effectively defend against rainbow table attacks. Still, it’s a valuable bonus.

To do this, you can either load a JavaScript file on the client side or make an AJAX request to an API when the user creates their password.

If your AJAX request sends the plaintext password, that’s a problem. It increases your attack surface.

Loading a JavaScript file can be heavy, so it’s better to load it asynchronously. However, even asynchronously, the file size (and therefore the number of compromised passwords it can check) is limited.

Naturally, since the client side is not secure, you must repeat the verification on the server side.

Another technique is to use the Have I Been Pwned service. It offers a technically elegant solution but, as it’s an Australian service using U.S.-based infrastructure, it raises serious concerns about GDPR compliance in Europe (we’ll cover this in a future article).

This service works using k-anonymity.

You hash the user’s password and send only the first k characters of the hash to the service. The service returns all hashes in its database that share those k starting characters. You then compare them to the user’s password hash; if any matches, the password has been compromised.

Example:

The password “framboise” has this SHA256 hash:

“F9E4141EE43A3B877758E2584A1F6A0E7A9C8D6E58BB859A1665D8C1F447003C”

The first 5 characters are “F9E41.”

You send those 5 characters to Have I Been Pwned.

It returns:

  • “F9E4141EE43A3B8777ERT8584A1F6A0E7A9C8D6E58BB859A1665D8C1F447003C”
  • “F9E4141EE962758777ERT8584A1F6A0E7A9C8D6E58BB859A1665D8C1F447003C”
  • “F9E4141EE43A3B877758E2584A1F6A0E7A9C8D6E58BB859A1665D8C1F447003C”

You compare and see that the third hash matches the user’s hash. Therefore, the password is compromised.

With this system, no password is ever transmitted over the network, and Have I Been Pwned never knows which password you’re testing. In the example, three results were returned, but the smaller the k value, the more results you get.

Then Store Passwords Properly

After the user has chosen a good password and securely submitted it, you must store it properly.

A fundamental rule: never store passwords in plain text. If an attacker gains access to your password storage, you’ve handed them access to every account on your service, and possibly other services (users unfortunately often reuse passwords).

So, what should you do?

  1. First, limit password length, but not too much. Drupal, for example, limits password length to 512 bytes, meaning between 128 and 512 UTF-8 characters. This step is necessary to prevent a DoS attack because hashing consumes resources (an attacker could input an extremely long password to exhaust system resources during hashing).

  2. Generate a random salt. A specific length string.

    Table salt also called pepper

    Table salt

    Table salt also called pepper
    You can generate a salt per database (for all passwords) or per password. It’s added to the password to ensure identical passwords have different hashes. Salts prevent hash comparisons, table-wide or row-wide, even if users choose the same password or reuse it across services.

    Example:

    Table-level salt:

    Alice uses “framboise” on both “nice-raspberries-squashed.com” and “mean-raspberries-squashed.com”.

    “nice-raspberries-squashed.com” generates salt “458,” making Alice’s password “458framboise.”

    “mean-raspberries-squashed.com” generates salt “gdf,” making Alice’s password “gdfframboise.”

    If a hacker accesses both tables, the hashes won’t match (since the salted passwords differ), so they won’t know Alice reused the same password.

    Row-level salt:

    Alice and Bob both use “framboise” on “nice-raspberries-squashed.com.”

    The site generates salt “458” for Alice’s password → “458framboise.”

    It generates salt “sdd” for Bob’s password → “sddframboise.”

    If an attacker accesses the table, they won’t know Alice and Bob used the same password because their hashes are different.

  3. Hash the combined salt + password using a secure algorithm like SHA512.

    For password “framboise” with salt “458”:

    hash(sha256, ‘458framboise’) = ‘527D88733ED03CE5EF2D12AE4279950ABC29DC42817B14A58AB2150678A7CC72486CE637B8AD10333E80879F3D0CE685FA6CBB5DB8D7954382C2116616F8F6CF’

  4. You now have a reasonably secure stored password. Also store the salt. To strengthen the hash further, you can iterate hashing with the previous hash and the password:

    hash(sha256, ‘527D88733ED03CE5EF2D12AE4279950ABC29DC42817B14A58AB2150678A7CC72486CE637B8AD10333E80879F3D0CE685FA6CBB5DB8D7954382C2116616F8F6CF framboise’) = ‘E3A252D3C455D638C82D8430DDC85E9B5B13F6508D690D97E2C5A1CC8AEE4A26D4C723E4EE4F524B4926C0357B2651132DF67446721BB4D3BCDD017C4A0E0E1B’

    The goal is to make the resulting hash different from any known hashes attackers might have, making password recovery harder.

    When a user tries to log in again, your system repeats the process (using the stored salt) and compares the new hash to the stored one. If they match, the password is correct.

Common Mistakes to Avoid

Sometimes we make silly mistakes! Here are some to avoid:

  • When changing the complexity threshold for accepted passwords, don’t apply it to old password fields. Otherwise, users won’t be able to enter their old password to change it. Only apply the threshold to new password fields.

  • If you change your password storage method or complexity rules and need user action, notify them by email. Don’t send password reset links with long expiration—users may click them months later, giving attackers time to exploit them.

  • Never send a user’s password via email. Emails are like postcards: anyone can read them. And if you can email a password, it means you’re not storing it securely.

  • Provide meaningful error messages when users create passwords. Use labels like “weak,” “strong,” or “very strong,” not entropy scores users won’t understand.

  • Ensure passwords are transmitted via HTTPS. Ban HTTP completely.

  • For extra security, implement authentication chains, meaning two-factor authentication (e.g., with a smartphone token) in addition to a password.