简体   繁体   中英

Is my password/authentication strategy for a REST API good?

Okay, good is subjective, but it fits better in the title. What I really want to know is if there are any glaring deficiencies in this password management/authentication strategy. I am also curious about performance. I am trying to use PBKDF2 to authenticate but I am not really sure if that is a good idea for a RESTful web service. I can find some parts of this question answered here and there but I have never found one comprehensive top to bottom answer. Here goes, this is my entire strategy.

Background:

  • Service is an ASP.NET Web Api project in C#
  • Backend is MS SQL server
  • Credentials are sent over HTTPS using Basic authentication, ie 'authorization: Basic username:password'
  • API will be consumed by Andriod app, ios app and web site

First, the backend:

create table [dbo].[User] (
    [Name] varchar(50) collate SQL_Latin1_General_CP1_CI_AI not NULL primary key clustered,
    [Password] varchar(28) collate SQL_Latin1_General_CP1_CS_AS not NULL,
    [Salt] varchar(28) not NULL)

the stored procedure to create the user:

create procedure [dbo].[User_Create] 
    @Name varchar(50),
    @Salt varchar(50),
    @Password varchar(50)
as 
    insert into [dbo].[User]([Name], [Salt], [Password])
    values(@Name, @Salt, @Password)

and the stored procedure to get the user:

create procedure [dbo].[User_Get] 
    @Name varchar(50)
as 
    select *
    from [dbo].[User]
    where [Name] = @Name

With the backend I am curious if the data types selected for the name, password and salt are appropriate.

This is the code that creates a new user and persists it to the backend. This is probably what I am most concerned about, both from a security stand point as well as performance.

public void CreateUser(string username, string password)
{
    int hashLength = 20;
    int saltLength = 20;
    int hashIterations = 1000;
    using(SqlConnection connection = new SqlConnection(this._ConnectionString))
    using (SqlCommand command = new SqlCommand("[dbo].[User_Create]"))
    {
        Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, saltLength, hashIterations);
        string salt = Convert.ToBase64String(pbkdf2.Salt);
        string hashPassword = Convert.ToBase64String(pbkdf2.GetBytes(hashLength));

        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@Name", username);
        command.Parameters.AddWithValue("@Salt", salt);
        command.Parameters.AddWithValue("@Password", hashPassword);
        connection.Open();
        command.ExecuteNonQuery();
    }
}

And here is the code to authenticate the user. This essentially just executes the stored procedure, calls ValidatePassword to perform the PBKDF2 and returns a User object back to the Web API message handler which sets the Principal (roles have not been implemented yet):

public User AuthenticateUser(string username, string password)
{
    using (SqlConnection connection = new SqlConnection(this._ConnectionString))
    using (SqlCommand command = new SqlCommand("[dbo].[User_Get]"))
    {
        command.CommandType = CommandType.StoredProcedure;
        command.Parameters.AddWithValue("@Name", username);
        command.Connection = connection;

        connection.Open();
        using (SqlDataReader reader = command.ExecuteReader())
        {
            if (reader.Read() && this.ValidatePassword(password, reader["Salt"].ToString(), reader["Password"].ToString()))
            {
                return new User()
                    {
                        Name = username,
                        CustomerId = reader["CustomerId"].ToString()
                    };
            }
            else
            {
                return null;
            }
        }
    }
}

And here is ValidatePassword , which the above code relies on to validate the password (in case that wasn't obvious). I also want to make sure I got this one right.

private bool ValidatePassword(string password, string salt, string hashedPassword)
{
    int hashLength = 20;
    int hashIterations = 1000;
    byte[] saltBytes = Convert.FromBase64String(salt);
    Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, hashIterations);

    byte[] hashBytes = pbkdf2.GetBytes(hashLength);
    string hash = Convert.ToBase64String(hashBytes);

    // Security Decisions For String Comparisons
    //
    // If you are making a security decision (such as whether to allow access to a system resource) based on the 
    // result of a string comparison or a case change, you should not use the invariant culture. Instead, you 
    // should perform a case-sensitive or case-insensitive ordinal comparison by calling a method that includes 
    // a StringComparison parameter and supplying either StringComparison.Ordinal or 
    // StringComparison.OrdinalIgnoreCase as an argument. Code that performs culture-sensitive string operations 
    // can cause security vulnerabilities if the current culture is changed or if the culture on the computer 
    // that is running the code differs from the culture that is used to test the code. In contrast, an ordinal 
    // comparison depends solely on the binary value of the compared characters.
    //
    // Source: http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.invariantculture.aspx

    return hash.Equals(hashedPassword, StringComparison.Ordinal);
}

highly opinionated answer.

why do this yourself when there are many off the shelf (as in completely free and well tested) implementations?

By all means do this as an exercise (it a fascinating area imo) but timing attacks are just the tip of the iceberg.

the first rule of security is if you are doing anything involving encryption yourself you are probably venturing into an area that you should ensure you do very little in. Eg use an owin or open auth provider or active directory. But step away from the weapon.

dont become the next muppet to leak a ton of data by doing it yourself.

have a look at federated security, saml tokens and sts providers

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM