Home > Blog > 2014 > Jun > 20

Improving Security with Login Attempts, Password Salting and Upgrading from MD5

by Ira Endres on Friday, June 20, 2014

Why Web Application Security Matters

If you are storing clear-text or MD5-hashed passwords, it's only a matter it time before you lose control of the system.

Recently, I had a discussion with my local internet service provider (ISP). At my house, I have a security system with cameras and I was able to set up my system to log in remotely by purchasing a static IP address from them; they of course were concerned I was hosting a "website" from my cable modem service. This in turn lead to a discussion about digital video recorders (DVR) and how remote access on DVR's works and additional security concerns about unauthorized access. For anyone owning a DVR and done something similar, you might have already had this discussion with your ISP but for the benefit of those unfamiliar, DVRs allow remote access via an internal web server.

Since security was my primary focus, plugging a DVR directly to the internet's unfettered access was a big concern due to the unrelenting SSH, port 80, FTP, et cetera access attempt from China and Russia. Specifically, the concern was unauthorized access to the DVR through dictionary attacks, brute force or some other mechanism. I asked my ISP's representative, "Does my DVR lock the account after multiple access attempts? Does my DVR have any security holes or back doors that might allow unauthorized viewing? Does my DVR store passwords in MD5 or in clear text? Does it salt those passwords?" Since this was not the case for my DVR, I implemented an additional security layer between my DVR and the internet to help prevent unauthorized access.

Why does password security matter? Because if someone were to gain access to your database or passwords file and your are storing clear-text or MD5-hashed passwords, it's only a matter of time before your lose control of the system. Look at this enterprising solution for a reason not to use unsalted MD5 passwords; it literally Google's the password hash and returns the equivalent password. Look at this Wikipedia article on how you can generate a specific MD5 or a SHA-1 hash by padding the contents of the binary data. Even if you do salt MD5 or SHA passwords and you salt with an application global value, an MD5 or SHA database look up could be populated from either a dictionary of commonly used passwords or generated passwords and could easily be circumvented.

Solution: Stronger Hashing Algorithms and User-Specific Salts

When designing your next secure application or upgrading the security of an existing application, please take into consideration these new security patterns for password hashing:

ApplicationUser

 ID | EMAIL         | PASSWORD_SALT     | PASSWORD_HASH       | LOCKED
------------------------------------------------------------------------
 1  | bob@smith.com | 9383883c26b347... | 63d153331876a14...  | 0

As you can see from this database table design and sample entry, there are two columns dedicated to passwords for the ApplicationUser database table, one for the user's password hash and one for the user's password salt. When a user account is created, the application should create a unique string for that specific user for the purpose of salting just their password. When the user authenticates through the system, the application will take their salt and password, hash them, and then compare them to the predetermined password hash from when they last updated their password.

Password Hashing and Salting Example

Say we have the following class:

public class ApplicationUser
{
    public long ID { get; set; }

    [EmailAddress]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    public string PasswordHash { get; set; }

    public string PasswordSalt { get; set; }
}

When user registration occurs, after validation of emails, passwords, confirm passwords, et cetera, the application only needs to add an extra step for creating the password salt in addition to the normal hashing that occurs behind the scenes. As you can see in the example below, this sample utilizes a formatted Globally Unique Identifier (Guid) as the password salt, however a password salt can be a string of any size or having any special characters; since the string is converted to a UTF-8 encoded byte[] array, you could hash it directly with your own byte[] array values by adding the values before or after your password for additional randomization.

public ApplicationUser CreateApplicationUser(string email, string password)
{
    if (string.IsNullOrEmpty(emal))
        throw new ArgumentNullException("The email value was not provided.");

    ApplicationUser user = new ApplicationUser();
    user.Email = email;
    user.PasswordSalt = Guid.NewGuid().ToString().Replace("-", string.Empty).ToLower();
    user.PasswordHash = GenerateSha512Hash(password, user.PasswordSalt);
    return user;
}

protected string GenerateSha512Hash(string password, string salt)
{
    if (string.IsNullOrEmpty(password))
        throw new ArgumentNullException("The password value was not provided.");
    if(string.IsNullOrEmpty(salt))
        throw new ArgumentNullException("The salt value was not provided.");

    SHA512 sha = SHA512.Create();
    byte[] data = sha.ComputeHash(Encoding.UTF8.GetBytes(salt + password));
    StringBuilder sBuilder = new StringBuilder();
    for (int i = 0; i < data.Length; i++)
    {
        sBuilder.Append(data[i].ToString("x2"));
    }
    return sBuilder.ToString();
}

By implementing a per-user hashing and salting mechanism, we prevent even authorized persons from viewing and potentially acquiring user passwords, adding additional security and robustness to any application.

Failed Attempt? Make them wait.

Another authentication tool for preventing unauthorized access is forcing an extending login attempt window that logs failed authentication attempts for a particular session, forcing the user to wait longer for the more failed password attempts they have. Consider the following database structure:

LoginAttempt

 ID | SESSION_ID | CREATED
-------------------------------------------
 1  | 52ef69c... | 2014-06-20 13:59:27.023

Here we would store each failed login attempt from a particular session; a session could be instantiated with or without a valid authentication attempt via a stateless cookie. Then we have a procedure that for a given SESSION_ID returns the amount of time that the user must wait to attempt to login again.

create procedure p_GetFailedLoginAttemptWaitTime (
   @SESSION_ID bigint
)
as
begin

    declare @attempts int;

    select @attempts = COUNT(*)
    from LoginAttempt
    where SESSION_ID = @SESSION_ID and CREATED > DATEADD(DAY, -1, GETUTCDATE())

    if(@attempts < 2)
    begin
        set @attempts = 0
    end
    else 
    begin 
        set @attempts = @attempts - 1;
    end

    select @attempts * 30 as WAIT_TIME;

end

In this example, having just 2 failed login attempts would return a value of 30 seconds for waiting before allowing another login attempt. Each subsequent failed login attempt would force the user to wait an additional 30 seconds for each attempt. Of course, we limit the check to only show failed login attempts for the past day, which could be configured to any other time frame. By utilizing this extending login attempt window, we really do add additional security to our application.

Is Your Authentication Mechanism Secure?

Having explained all of this to this poor person on the phone, whose only job was to get me to shut down my dummy website sitting between my DVR and the internet, I believe I might have over-emphasized my position, but when it comes to application security and protecting your digital assets and data, laziness will not do. Until the next iteration in authentication security comes along, we must continue to encourage the design and implementation of secure user account registration and password handling.

And if you are still using clear-text, unsalted, or MD5 hashes for your application, please consider upgrading the security of your application with the above suggestions to help protect you and your application from unauthorized access.