Question

I have a question regarding using Login Names to protect passwords.

  1. You salt the Login Name with a shared salt and then hash it with BCrypt.

  2. You then take the original plain text Login Name and use it as a key to encrypt the password with AES. The result is then salted with a unique salt and finally hashed with BCrypt.

  3. The user's Display Name is set to their User ID (integer), as opposed to their Login Name, when the account is created. The user can change it later, except it can not match anyone else's Display Name or too closely match a case-insensitive comparison to their Login Name.

    My question is, if the database were compromised, would this make it significantly harder to recover the passwords than storing plaintext usernames and uniquely salted and BRcypted passwords?

Was it helpful?

Solution

This procedure appears to be very complicated. Actually you are adding a server side secret to the stored hashes (your procedure to hash passwords). That will indeed increase security, as long as the procedure stays secret. In other words, if the attacker has only read access to the database (SQL-injection) you have an advantage, as soon as he has got privileges on the server and knows the code, there is no advantage at all.

There is a much easier and safer way to get this advantage though. Just calculate a BCrypt hash with a random salt (most implementations will do that anyway), then encrypt this hash-value with a server-side key (AES for example). The key should not be derrived form other parameters, instead use a long and random enough key.

I tried to explain the reasons in my tutorial about safely storing passwords, maybe you want to have a look at it.

Edit:

Well i understand now, that you want to handle the login-name like a second password, and will not store it plaintext, instead you store only a BCrypt hash of it with a global "salt".

Lets assume that you are willing to spend 1 second of CPU time for password hashing. In your scheme you would have to split it to half a second for hashing the login-name and half a second for the password. An attacker has to brute-force the login-name first and the password in a second step.

  1. Login-names are normally very weak passwords. While people are learning that passwords need to be strong, login-names often contain only a name with a number and are short.
  2. You need to find the database record with the hash of the login-name, so you cannot use a random salt, instead you need to use a global "salt". This allows to build a single rainbow table to crack all usernames in one go. Important login-names like "admin" can be precalculated.
  3. To avoid duplicates, you have to uppercase/lowercase the login-name, this reduces the search space even more.

That means you spend half a second for a very weak password (login-name), then half a second for the normal password. Compared to investing 1 second for the normal (hopefully strong) password, you are probably decreasing security, by all means i cannot see any advantage.

OTHER TIPS

The authentication code would look something like this.

public static LoginResult TryLogin(string loginName, string pwd)
    {
        string loginHash = BCrypt.Net.BCrypt.HashPassword(loginName, SHARED_SALT);

        WidgetDataContext dc = new WidgetDataContext();
        var record = (from rec in dc.usp_GetUserByLoginName(loginHash)
                      select rec).SingleOrDefault();

        if (record == null)
            return new LoginResult(null, "Invalid Login Name/Password");

        if (record.FailedLoginCount >= MAX_CONSECUTIVE_LOGIN_FAILURES)
            return new LoginResult(null, "You have exceeded your maximum number of Login failures.  Your account is locked.");

        if (record.Locked) // In case account is locked for another reason
            return new LoginResult(null, "Your Account is locked.");

        pwd = EncryptionServices.Encrypt(pwd, loginName);
        pwd = BCrypt.Net.BCrypt.HashPassword(pwd, record.Salt);

        if (pwd == record.Password)
        {
            record.FailedLoginCount = 0;
            dc.SubmitChanges();
            return new LoginResult(record.UserId, "Login Successful");
        }

        record.FailedLoginCount++;
        dc.SubmitChanges();

        return new LoginResult(null, "Invalid Login Name/Password");
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top