5

Im a CS major and I just got done designing an ASP.net site, and for the site I needed a login authentication system... I didn't want to use SQLMembershipProvider as I really wanted to learn how to make one on my own... Anyways this is what I came up with, and I was wondering if anyone can give me some feedback, tips, or advice.

Thanks in Advance

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Security.Cryptography;

/// <summary>
/// Summary description for PwEncrypt
/// </summary>
public class PwEncrypt
{
    public const int DefaultSaltSize = 5;

    private static string CreateSalt()
    {
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        byte[] buffer = new byte[DefaultSaltSize];
        rng.GetBytes(buffer);
        return Convert.ToBase64String(buffer);
    }

    public static string CreateHash(string password, out string salt)
    {
        salt = CreateSalt();
        string saltAndPassword = String.Concat(password, salt);
        string hashedPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(saltAndPassword, "SHA1");
        hashedPassword = string.Concat(hashedPassword, salt);
        return hashedPassword;
    }
       public static string CreateHashAndGetSalt(string password, string salt)
    {

        string saltAndPassword = String.Concat(password, salt);
        string hashedPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(saltAndPassword, "SHA1");
        hashedPassword = string.Concat(hashedPassword, salt);
        return hashedPassword;
    }

    public static bool comparePassword(string insertedPassword, string incUserName, out string newEncryptedPassword, out string originalPassword)
    {
        databaseInteraction DBI = new databaseInteraction();
        string actualPassword ="";
        string salt = "";


        DBI.getSaltandPassword(incUserName, out salt, out actualPassword);
        string hashedIncPassword = PwEncrypt.CreateHashAndGetSalt(insertedPassword, salt);
       // hashedIncPassword = string.Concat(hashedIncPassword, salt);     
        newEncryptedPassword = hashedIncPassword;
        originalPassword = actualPassword;
        if (newEncryptedPassword == originalPassword)
        {

            return true;
        }

        else { return false; }

    }
6
  • If you want to create custom membership provider, you first have to inherit from MemberShipProvider. And this is quite well documented if you google a bit. Commented Jun 22, 2012 at 7:42
  • 2
    Don’t attempt to roll your own custom authentication and session management schemes or build your own controls unless you really have no other choice. I just want to do it, doesn't qualify. An interesting read: troyhunt.com/2010/07/… Commented Jun 22, 2012 at 8:06
  • @ChristopheGeers Exactly... It's pretty cumbersome to do it yourself, the built in MembershipProviders work well, they jut have to be configured to do so. Commented Jun 22, 2012 at 8:25
  • 1
    I understand that for a production site, this would indeed be the case but for educational purposes do you truly believe that only knowing how to implement an authentication system via a pre-constructed system is a good thing? Since I clearly mentioned the MembershipProviders I clearly knew what it was and am quite comfortable using it, but not every language has it... I was simply asking for advice or tips on implementing a custom system for the purposes of education OUTSIDE asp.net. As far as cumbersome to do it yourself, that's not really an issue as long as it may have some benefit later. Commented Jun 22, 2012 at 9:27
  • @user1474120 - Except your implementation security wise is a nightmare. Your password hashing is not sure. Commented Jun 22, 2012 at 11:22

1 Answer 1

1

Suppose we have a table for accounts like this

enter image description here

Then you can create some helper class that returns an account object

private static Account GetByName(string accountName, bool activatedOnly = false)
{
    using (var context = new DBEntities())
    {
        return context.Accounts.FirstOrDefault(s => 
            s.AccountName == accountName &&
            s.IsApproved == activatedOnly);
    }
}

public static Account Get(string accountName, string password)
{
    var account = GetByName(accountName, true);
    if (account != null)
        if (!Cryptographer.IsValidPassword(password, 
                                           account.PasswordSalt, 
                                           account.PasswordKey))
            return null;
    return account;
}

I'm using EntityFramework but it's not important here. The main idea is show that you don't need to get the whole list of accounts (especially if you have a big list of users).
My Cryptographer class looks like

public class Cryptographer
{
    private const int keyByteLength = 20;
    public static void Encrypt(string password, 
                               out byte[] salt,
                               out byte[] key)
    {
        using (var deriveBytes = new Rfc2898DeriveBytes(password, 
                                                        keyByteLength))
        {
            salt = deriveBytes.Salt;
            key = deriveBytes.GetBytes(keyByteLength);  
        }
    }
    public static bool IsValidPassword(string password, 
                                       byte[] salt, 
                                       byte[] key)
    {
        using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
        {
            byte[] newKey = deriveBytes.GetBytes(keyByteLength);  
            return newKey.SequenceEqual(key);
        }
    }
}

Of cource you can implement the algorithm of your own.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, this is exactly the type of feedback I was looking for... Regarding using Rfc2898DeriveBytes vs RNGCryptoServiceProvider and FormsAuthentication.HashPasswordForStoringInConfigFile... Which has the stronger hashing algorithm ? As far as getting a whole list of users, other methods actually handle getting the specific user to check the password against, the incUserID limits the password check to only that one, so I mean it does select against the whole database with only 1 return but the EntityFramework would do exactly the same thing wouldnt it?
@user1474120 - The main problem you are going to have is writting secure SQL queries. Based on your own code, your not using a secure password schema which is a problem, but the getting the user and password in a secure way is a bigger problem.
@user1474120, take a look at this
"with only 1 return". Exactly. You need to get only one record from your database (select top 1 * from Accounts where AccountName = @accountName).
correct me if im wrong, but don't you not need the select top 1 if you have a unique constraint upon the userName? Or is it part of a security thing that i'm not aware of? (this is more than totally possible)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.