Click here to Skip to main content
Click here to Skip to main content

Secure Password Authentication Explained Simply

By , 25 Jan 2010
 

Introduction

It's unbelievable to me that I am still running into websites that store passwords themselves, and you see rules like "password must be no more than 16 characters" or "passwords must not include '<> *& etc.." Just last night, I ran into a government website that had a maximum password length of 12 characters, and while they allowed certain special characters, others were disallowed. These kinds of rules sound like the site is storing the password in plain text, or worse, not using parameters for the storage and validation of the passwords from a database.

After so many years of passwords, I would have assumed most programmers would have figured these things out. In many cases, storing passwords in your own application or website is pointless; there are many alternatives.

  1. Integration with your network security, such as ActiveDirectory
  2. OpenID

This article is for those who must store usernames and passwords themselves. I am going to use C#, MS Access, and ASP.NET for this example; though the premise would obviously be able to be used with any application stack you like.

Background

Other articles have been written covering this information, but I want to keep this article very straightforward. I am not going to go into the depths of cryptography, nor am I going to cover keeping people signed in, or validating minimum complexity, or anything of that nature. My intention is to keep the information and code to only what is required to store passwords securely. For the purposes of this article, my goals are very simple.

  1. Allow passwords of unlimited length, and of any characters the user likes.
  2. Be able to store the password in a database efficiently.
  3. Prevent any SQL injection attacks.

So, let's start with the first 2 items. I don't believe that passwords should ever need to be "read" or "recovered". The only exception to this would be if we need to keep passwords to other applications, and even then, only if you have no other choice, such as converting all the applications in question to OpenID. Should a password be lost or compromised, there should be a "reset", this philosophy means that I do not need to ever store passwords in plain text or a decryptable form, at least not for authentication. Thus, the simplest way to solve these two problems is by using a one way hash, that is an encryption that can't be decrypted. Hashing algorithms like MD5, SHA-1, and others can be used to create a fixed length series of values that is more of a unique signature for data rather than encrypted data.

For example, the MD5 hash of "test" is "098f6bcd4621d373cade4e832627b4f6". The nice thing about these signatures is that no matter how big the data is, the size of the data returned is always the same. This is why you will see MD5 and SHA-1 signatures for validating large files like Linux DVD ISO images. Different algorithms will have different lengths of output signatures:

  • MD5 has a length of 128 bits, which is 16 bytes. Interestingly enough this is the same length as a UUID/GUID. This means that if you are storing something non-security related, you can store the data in a UUID/GUID field in your database.
  • SHA-1 has a length of 160 bits, which is 20 bytes.
  • SHA-256 has a length of 256 bits, which is 32 bytes.
  • SHA-384 has a length of 384 bits, which is 48 bytes.
  • SHA-512 has a length of 512 bits, which is 64 bytes.

Now, if we use a signature to store our password, we get all three of my requirements in one shot. First, because the signatures don't contain anything that could be used for SQL injection or that we have to worry about encoding, any character the user wants to use can be used for a password. Second, we can store passwords of any size we like in a fixed length field, such as 64 bytes.

Before going any further, we need to look at the fact that MD5 is now easily cracked. In fact, you can decrypt many MD5 signatures at websites like this one. SHA-1 is also showing signs of being weakened, and will soon be obsolete as well. So, what can be done to "shore up" the signature?

First, we use the strongest hash available to us in our code base. Obviously, we could write our own implementation of stronger ones, but it's better to use tried and true encryption code rather than make our own.

The second thing we can do is to "salt" our passwords; in short, this means creating a random value to append to the end of the password to make it more unique. This could be a short series of bytes, but we can use as much data as is reasonable. Of course, the other reason to "salt" passwords is to prevent analysis of the passwords. If all you do is hash the password, then the password of "test" is always "098f6bcd4621d373cade4e832627b4f6". This means that should the list of passwords be compromised, everyone who has the same password would have the same hash. By using a random salt value, the stored hashes become unique, even if the same password is used.

The most common question often asked about salts is "If a salt is random, how do you reliably generate the same salt every time verification is done"? The answer is simple, you don't. You store the salt separately from the password hash.

Storing the data is fairly straightforward, but because we are using an array of bytes (an OLE object in Access), pure text SQL will not work. Instead, we need to use parameters, this is good since this also helps prevent SQL injection.

Since I want this article to cover more than just rehashing the same data that was covered everywhere else, I figured I would make this example more portable to other databases. See my previous article for more details.

Thus, I will start with a static DB class that reads the configuration file and creates the proper types based on the provider.

public static class DB
{
    private static DbProviderFactory _factory = null;
    private static string _connectionString = null;
    private static string _quotePrefix = string.Empty;
    private static string _quoteSuffix = string.Empty;

    public static DbProviderFactory Factory
    {
        get
        {
            if (_factory == null)
            {
                ConnectionStringSettings connectionSettings = 
                      ConfigurationManager.ConnectionStrings["DSN"];
                _factory = DbProviderFactories.GetFactory(
                                  connectionSettings.ProviderName);
                _connectionString = connectionSettings.ConnectionString;
            }
            return _factory;
        }
    }

    public static string ConnectionString
    {
        get
        {
            return _connectionString;
        }
    }

    public static string QuotePrefix
    {
        get
        {
            if (string.IsNullOrEmpty(_quotePrefix))
            {
                FillQuotes();
            }
            return _quotePrefix;
        }
    }

    public static string QuoteSuffix
    {
        get
        {
            if (string.IsNullOrEmpty(_quoteSuffix))
            {
                FillQuotes();
            }
            return _quoteSuffix;
        }
    }

    //this function gets the proper characters to wrap
    //database, table, and column names.
    private static void FillQuotes()
    {
        var cb = Factory.CreateCommandBuilder();
        if (!string.IsNullOrEmpty(cb.QuotePrefix))
        {
            _quoteSuffix = cb.QuoteSuffix;
            _quotePrefix = cb.QuotePrefix;
            return;
        }
        using (var conn = GetConnection())
        {
            using (var cmd = conn.CreateCommand())
            {
                //test to see if we can wrap names in square brackets.
                cmd.CommandText = "SELECT '1' as [default]";
                try
                {
                    using (var dr = cmd.ExecuteReader())
                    {
                        while (dr.Read())
                        {

                        }
                    }
                    _quotePrefix = "[";
                    _quoteSuffix = "]";
                }
                catch
                {
                    try
                    {
                        //square brackets failed, try double quotes.
                        cmd.CommandText = "SELECT '1' as \"default\"";
                        using (var dr = cmd.ExecuteReader())
                        {
                            while (dr.Read())
                            {

                            }
                        }
                        _quotePrefix = _quoteSuffix = "\"";
                    }
                    catch
                    {
                        //no characters appear to work
                    }
                }
            }
        }
    }

    private static DbConnection GetConnection()
    {
        DbConnection conn = Factory.CreateConnection();

        conn.ConnectionString = ConnectionString;
        conn.Open();
        return conn;
    }

    public static int ExecuteNonQuery(string sql, 
                  IEnumerable<DbParameter> parameters)
    {
        using (var conn = GetConnection())
        {
            DbCommand cmd = null;
            try
            {
                cmd = conn.CreateCommand();
                cmd.CommandText = sql;
                foreach (var parameter in parameters)
                {
                    cmd.Parameters.Add(parameter);
                }
                return cmd.ExecuteNonQuery();
            }
            finally
            {
                if (cmd != null)
                {
                    cmd.Parameters.Clear();
                    cmd.Dispose();
                }
                cmd = null;
            }
        }
    }

    public static DbDataReader ExecuteReader(string sql, 
                  IEnumerable<DbParameter> parameters)
    {
        var conn = GetConnection();
        DbCommand cmd = null;
        try
        {
            cmd = conn.CreateCommand();
            cmd.CommandText = sql;
            foreach (var parameter in parameters)
            {
                cmd.Parameters.Add(parameter);
            }
            return cmd.ExecuteReader(CommandBehavior.CloseConnection);
        }
        finally
        {
            if (cmd != null)
            {
                cmd.Parameters.Clear();
                cmd.Dispose();
            }
            cmd = null;
        }
    }
}

This class is not complete; therefore, it is not good for use in production code. It lacks the ability to use multiple connection strings, and more importantly, it doesn't support transactions. Probably, the biggest shortcoming is the complete lack of error handling. It does, however, have a couple really nice features; it takes full advantage of connection pooling, and it exposes the Quote characters for the provider.

This example only uses one table: "Users".

Users
Unique Constraint ID UUID/GUID
Primary Key user varchar(255)
  password byte[64]
  salt byte[16]

I intentionally made the column names collide with SQL keywords to show the functionality of wrapping column and table names. Therefore, you must wrap the column names correctly.

Using the code

Before we can authenticate a user, we must register them. To register a user, we have to do the following:

  • Get the username
  • Get the password
  • Generate a random salt
  • Create the password hash
  • Store the username, hash, and salt in the database

Surprisingly, this comes down to a very small block of code. You will see two odd things in the following code. I am not using any hardcoded provider types, and I am using RNGCryptoServiceProvider. The .NET Random object provides pseudo-random numbers; this would be fine, except they will repeat every time you create a new object. To solve this problem, Microsoft tells you to either use RNGCryptoServiceProvider or simply create a single static Random object that all the code in your project uses for random numbers.

bool successful = false;
try
{
    string insertUserSQL = 
        string.Format(
            "INSERT INTO {0}Users{1} ({0}user{1}," + 
            "{0}salt{1},{0}password{1}) VALUES (?,?,?)", 
            DB.QuotePrefix, 
            DB.QuoteSuffix);
    List<DbParameter> parameters = new List<DbParameter>();
    
    //you can change this line out for any Hash algorithm you like.
    HashAlgorithm hashAlgorithm = SHA512.Create();
    byte[] b = new byte[16];
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    rng.GetBytes(b);

    //create all 3 parameters, the order is critical since 
    //these are positional parameters.

    //user
    DbParameter p = DB.Factory.CreateParameter();
    p.DbType = DbType.String;
    p.Value = txtUserName.Text;
    parameters.Add(p);

    //salt
    p = DB.Factory.CreateParameter();
    p.DbType = DbType.Binary;
    p.Value = b;
    parameters.Add(p);

    //password
    p = DB.Factory.CreateParameter();
    p.DbType = DbType.Binary;
    p.Value = b;
    parameters.Add(p);

    string s = txtPassword.Text;

    List<byte> pass = new List<byte>(Encoding.Unicode.GetBytes(s));
    pass.AddRange(b);
    p.Value = hashAlgorithm.ComputeHash(pass.ToArray());

    DB.ExecuteNonQuery(insertUserSQL, parameters);
    successful = true;
}
catch(Exception ex)
{
    Debug.WriteLine(ex.ToString());
}
Label3.Text = "Registration " + 
  (successful ? "successful" : "failed");

The above code is as simple as possible. It should include checks for usernames already existing in the database and more.

The next thing is actually responding to a login request. Again, it is a surprisingly simple bit of code. Our list of things to do is:

  • Wait 2 seconds to "tarpit" attackers, thus slowing brute force attacks to a crawl.
  • Get the username and password from the user.
  • Get the correct record from the database.
  • Use the salt from the database to create a hash from the salt and password attempt.
  • Compare the resulting hash to the password hash that is stored in the database.
  • Return "login failed" or "login successful"; we don't give want to show "user not found" or "incorrect password" as that would give attackers too much information.

Here is the code to do all of that:

Thread.Sleep(2000); //tarpit
bool successful = false;
HashAlgorithm hashAlgorithm = SHA512.Create();
string retrieveUser = 
    string.Format(
        "SELECT {0}salt{1}, {0}password{1} FROM {0}Users{1} WHERE {0}user{1}=?", 
        DB.QuotePrefix, 
DB.QuoteSuffix);
List<DbParameter> parameters = new List<DbParameter>();

try
{
    DbParameter p = DB.Factory.CreateParameter();
    p.DbType = DbType.String;
    p.Value = txtUserName.Text;
    parameters.Add(p);        
    using (DbDataReader dr = DB.ExecuteReader(retrieveUser, parameters))
    {
        while (dr.Read())
        {
            byte[] salt = (byte[])dr.GetValue(0);
            byte[] password = (byte[])dr.GetValue(1);
            List<byte> buffer = 
              new List<byte>(Encoding.Unicode.GetBytes(txtPassword.Text));
            buffer.AddRange(salt);
            byte[] computedHash = hashAlgorithm.ComputeHash(buffer.ToArray());
            bool tmp = true;
            tmp = (computedHash.Length == password.Length);
            if (tmp)
            {
                for (int i = 0; i < computedHash.Length; i++)
                {
                    tmp &= computedHash[i] == password[i];
                    if (!tmp)
                    {
                        break;
                    }
                }
            }
            successful = tmp;
        }
    }
}
catch (Exception ex)
{
    Debug.WriteLine(ex.ToString());
}
Label3.Text = "Login " + 
  (successful ? "successful" : "failed");

Points of interest

As stated above, an MD5 hash is the same size as a GUID, and could actually be used to fill in the ID if your database doesn't have an equivalent to SQL Server's newid().

I have seen people state that the "ID" of the user's row could be used as a "salt". However, if you try to use the ID value as the salt, be it a GUID or an auto-increment value, it bypasses the security afforded by a totally random seed. Even if my opinion is wrong, generating a series of random bytes, as above, is easy enough to do, that it still doesn't seem like using the "ID" is a good idea.

Because I am using a parameter for the values, I can use any value I like for the username, be it an email address, or something with normally disallowed characters, such as an apostrophe like in "O'Brian".

Remember to use SSL to secure any page that deals with sensitive information, especially passwords since people tend to reuse passwords. Cost is not an excuse. While some sites like Verisign and Thawte charge over $100 (US) to purchase an SSL certificate, you can get cheap certificates from GoDaddy for around $30 (US) or free from StartSSL, and just like Verisign and Thawte, they work in all major browsers as well. Before the CAcert crowd starts responding, I don't recommend CA, because it will require all who use your site to insert the root certificate manually.

History

  • 2010-01-25: Added the "Remember SSL" point of interest.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alaric Dailey
Software Developer (Senior) Pengdows
United States United States
Member
Currently looking for new contracts in Omaha NE or telecommute opportunities.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSTorage of salt and choosing a saltmembergurjsing16 Sep '12 - 23:27 
In the article you suggest that " You store the salt separately from the password hash." But in your code, you choose to store the salt in the same table as the ID and encrypted password. So by the quoted message above, did you mean that the salt be stored in a different column? If that table's data is compromised, then even the salts are exposed too! Right?
 
If in real world the salts are not supposed to be stored in the same table, then can you please suggest some methods of safely storing the salts?
 
And if you really mean to suggest that salts can be stored in a separate column in the same table (or even a different table in the same database), then why are you averse to using the ID column, or email as a salt. A totally random salt, stored anywhere in the same database as the passwords, is as good as using ID/email as the salt.
 
Did I misunderstand something?
Gurjeet Singh
 
gurjeet.singh.im

AnswerRe: STorage of salt and choosing a saltmemberAlaric Dailey16 Oct '12 - 3:40 
If the data in the database is compromised, you are kind of screwed anyways. So whether it is in the same table or not is irrelevant.
 
Why am I adverse to using the ID as the salt? There are a number of reasons, lack of randomness, and the likelihood of it being changed without understanding that it is more than just a meaningless ID.
 
In my opinion, ID columns are identifiers, nothing more, they are used to identify the row, for referential integrity. They are NOT there to provide DATA. When you rely on the ID column having certain data in it, you can break code and have no idea why.
 
Using the salt, in the table gives an attacker no idea of HOW you are using the salt, you could be appending the salt to the data, prepending the salt to the data, XOR encoding salt into the data then hashing it, using it to generate an HMAC hash, or any one of a dozen other things, having the salt doesn't mean that it is any easier to hack the salted hash, unless of course you are always using the same hash.
 
Another thing that COULD be done, is to generate a new salt and hash with every successful login.
 
You are missing lots, I suggest you go out and look at hashing algorithms.
Generalthanks for sharing ...memberditsche4 Feb '10 - 9:02 
Hello Alaric Dailey, first thanks much for your nice article(s).
I have already read a lot of extensive article about connections and database layers at code project, but as net programming beginner with limited english vocabulary it is often not easy to understand, since most articles are written in C# and I am only able to start with Visual Basic 2008 Express Windows Applications, but that is OK, knowing one day C# is the goal for me too and there are translation websites.
What I wanted to say is that with these simple condensed scenarios are perfect to learn for myself and I think also for others starting with Databases.
I have decided to implement this concept in my project in terms of security and independence from the database provider. I think to extract the table and column names as name-templates into a class is also welcome, am I right?
I would like another example of this article as Windows Application with integrated error handling, transactions and using the class DataSourceInformation (ParameterMarkerPattern) out of the article "Using Information from the .NET DataProvider" in the DB class, for not only put the right quotePrefix/quoteSuffix. The password in the App.Config for the connectionString does not need to be
encrypted, it is safe in the App.Config?
I know, try to do by myself is the best, but I don't know how to implement reliable in your classes, maybe someone finds some time to show me.
Anyway I like much the way you write your articles, wish to see more of this, thanks a lot and please excuse my english. Smile | :)
GeneralRe: thanks for sharing ...memberAlaric Dailey4 Feb '10 - 11:41 
First, thanks for the compliments Smile | :)
 
Judging by what you are saying, you will likely find my article Don't hard code your DataProviders very useful as well. Now for some specific responses.
 

ditsche wrote:
I think to extract the table and column names as name-templates into a class is also welcome, am I right?

 

Yes, you can implement your tables and columns as classes, I have my own ORM that I am rewriting, that does exactly that. Though it's not yet ready for public release.
 

 
ditsche wrote:
The password in the App.Config for the connectionString does not need to be encrypted, it is safe in the App.Config?

 
Storing a password in plain text is NEVER a good idea, especially not in a windows application where users could get to the hard drive much easier than say a web site. That being said there are 2 acceptable solutions, the first and easiest is to use a trusted connection (also known as "Integrated Security"). Your only other choice is to encrypt your app.config file explained here
 

ditsche wrote:
I would like another example of this article as Windows Application with integrated error handling, transactions and using the class DataSourceInformation (ParameterMarkerPattern)

 
I will work on something like that.
 
THIS IS NOT THREAD SAFE, AND DOESN'T SUPPORT TRANSACTIONS it is only suitable as a proof-of-concept. My enhanced classes will be available soon, so I do not recommend you use this, or at very least, you need to wrap some of the calls with "lock" statements, so the Initialize won't run multiple times.
 
All of that being said here is the modifed DB class that initializes its DataSourceInformation class
 
  public static class DB
    {
        private static DbProviderFactory _factory = null;
        private static string _connectionString = null;
        private static DataSourceInformation _dataSourceInformation = null;
        private static string _quotePrefix;
        private static string _quoteSuffix;
        private static bool _initialized;
 
        public static DbProviderFactory Factory
        {
            get
            {
                if (_factory == null)
                {
                    ConnectionStringSettings connectionSettings =
                          ConfigurationManager.ConnectionStrings["DSN"];
                    _factory = DbProviderFactories.GetFactory(
                                      connectionSettings.ProviderName);
                    _connectionString = connectionSettings.ConnectionString;
                }
                return _factory;
            }
        }
 
        public static string ConnectionString
        {
            get
            {
                return _connectionString;
            }
        }
 
        public static string QuotePrefix
        {
            get
            {
                Initialize();
                return _quotePrefix;
            }
        }
 
        public static string QuoteSuffix
        {
            get
            {
                Initialize();
                return _quoteSuffix;
            }
        }
 
        public static string CreateProperParameterName(DbParameter parameter)
        {
            if (!_initialized)
            {
                Initialize();
            }
 
            //this will throw if a null is passed in
            string parameterName = (parameter.ParameterName ?? string.Empty).Trim();
            int length = _dataSourceInformation.ParameterNameMaxLength;
            if (length &lt; 1)
            {
                return "?";
            }
            if (string.IsNullOrEmpty(parameterName))
            {
                parameterName = "a" + Guid.NewGuid().ToString().Replace("-", string.Empty);
                if (length < parameterName.Length)
                {
                    parameterName = parameterName.Substring(0, length);
                }
                parameter.ParameterName = parameterName;
            }
 
            return string.Format("{0}{1}", _dataSourceInformation.ParameterMarkerPattern[0], parameterName);
        }
 
        public static string QuoteIdentifier(string identifier)
        {
            Initialize();
            var ss = identifier.Split('.');
            for (int i = 0; i < ss.Length; i++)
            {
                ss[i] = string.Format("{0}{1}{2}", _quotePrefix, ss[i], _quoteSuffix);
            }
            return string.Join(".", ss);
        }
 
        //this function gets the proper characters to wrap
        //database, table, and column names.
        private static void Initialize()
        {
            if (_initialized)
            {
                return;
            }
            _initialized = true;
            using (var conn = GetConnection())
            {
                _dataSourceInformation =
                    new DataSourceInformation(
                        conn.GetSchema(DbMetaDataCollectionNames.DataSourceInformation));
                var cb = Factory.CreateCommandBuilder();
                if (!string.IsNullOrEmpty(cb.QuotePrefix))
                {
                    _quoteSuffix = cb.QuoteSuffix;
                    _quotePrefix = cb.QuotePrefix;
                }
                else
                {
                    using (var cmd = conn.CreateCommand())
                    {
                        //test to see if we can wrap names in square brackets.
                        cmd.CommandText = "SELECT '1' as [default]";
                        try
                        {
                            using (var dr = cmd.ExecuteReader())
                            {
                                while (dr.Read())
                                {
 
                                }
                            }
                            _quotePrefix = "[";
                            _quoteSuffix = "]";
                        }
                        catch
                        {
                            _quotePrefix = _quoteSuffix = "\"";
                        }
                    }
                }
            }
        }
 
        private static DbConnection GetConnection()
        {
            DbConnection conn = Factory.CreateConnection();
 
            conn.ConnectionString = ConnectionString;
            conn.Open();
            return conn;
        }
 
        public static int ExecuteNonQuery(string sql,
                      IEnumerable<DbParameter> parameters)
        {
            using (var conn = GetConnection())
            {
                DbCommand cmd = null;
                try
                {
                    cmd = conn.CreateCommand();
                    cmd.CommandText = sql;
                    foreach (var parameter in parameters)
                    {
                        cmd.Parameters.Add(parameter);
                    }
                    return cmd.ExecuteNonQuery();
                }
                finally
                {
                    if (cmd != null)
                    {
                        cmd.Parameters.Clear();
                        cmd.Dispose();
                    }
                    cmd = null;
                }
            }
        }
 
        public static DbDataReader ExecuteReader(string sql,
                      IEnumerable<DbParameter> parameters)
        {
            var conn = GetConnection();
            DbCommand cmd = null;
            try
            {
                cmd = conn.CreateCommand();
                cmd.CommandText = sql;
                foreach (var parameter in parameters)
                {
                    cmd.Parameters.Add(parameter);
                }
                return cmd.ExecuteReader(CommandBehavior.CloseConnection);
            }
            finally
            {
                if (cmd != null)
                {
                    cmd.Parameters.Clear();
                    cmd.Dispose();
                }
                cmd = null;
            }
        }
    }
 
And your final statement creation block would like this
 
    List<DbParameter> parameters = new List<DbParameter>();
 
    var p = DB.Factory.CreateParameter();
    p.ParameterName = "user";
    //fill in data... AS SHOWN IN ARTICLE
    parameters.Add(p);
 
    p = DB.Factory.CreateParameter();
    p.ParameterName = "salt";
    //fill in data...
    parameters.Add(p);
 
    p = DB.Factory.CreateParameter();
    p.ParameterName = "password";
    //fill in data...
    parameters.Add(p);
 
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("INSERT INTO {0}Users{1} ({0}user{1},{0}salt{1},{0}password{1}) VALUES (",
        DB.QuotePrefix, DB.QuoteSuffix);
 
    string comma = "";
    foreach (var p1 in parameters)
    {
        sb.Append(comma);
        comma = ",";
        sb.Append(DB.CreateProperParameterName(p1));
    }
    sb.AppendLine(")");
 
    Debug.WriteLine(sb.ToString());
QuestionRe: thanks for sharing ...memberditsche5 Feb '10 - 5:12 
Thanks for the quick response and posting the modifed DB class, and giving people like me a chance to do things more flexible, am waiting anxiously for the things to come..
I do not want to steal much time of you for answering off-topic question, but would like to give a briefly description of my Project for understanding and ask something general:
I try to upgrade an old VBA/Access Auctionhouse-Application to Net. I have now 5 MDW-Backends (each ca 200-300 MB) with about 50 tables. To provide are customer management, auctions, articles, artists, invoices, contracts and much more (Reports, management of thousends of Images).
Should I think about a solution with a SQL server Backend? I'm unfortunately not very happy with the free version of SQL Server 2008 Express (each client has to install it to run in network, Management Studio to edit uncomfortable), but for developing tests it's OK and later export to better SQL-Servers or other DB's would be easier, also in view of an administrative user management can then directly resolved in the SQL server (Integrated Security), I think that had answered my own question?
Is it recommended to use a number of smaller applications or store everything in a big one?
I usually prefer unbound data in controls and editing one record fields in textboxes, but see here often a lot of data used to display and edited in controls like Grids or Lists. Does the newer Data Providers getting better in this case and does it not stress the network traffic more then necessary?
Am I able to use Blobs (Binary Large Objects) with parameter commands?
Does the Sharp Editor things better then the Visual Studio Editor or is it to prefer?
I know, this are a lot of question, but a keyword or simple yes/no would suffice me.
Many thanks in advance and greetings from Germany ...
AnswerRe: thanks for sharing ...memberAlaric Dailey5 Feb '10 - 7:01 
number of small applications vs one big one

I think you are asking about a number of small databases vs 1 big one. The BEST answer is "it depends". Security, backups, replication needs, ability to restore bits without affecting others using the system, skills of existing DBAs or ability and cost of getting DBAs, whether or not there is "enterprise" support, and ease of deployment all weigh into the decision. Should you choose to go with one big database, using schemas (namepaces) is good way to keep the separation you would have from multiple databases, and keep from having issues like duplicate table names. Even multiple databases can be done and with MS SQL Server and MySQL can have all work done thru a single connection. "Real" schema support like I am talking about requires a really good RDBMS, like PostgreSQL, Microsoft SQL Server or Oracle. You can see a nice breakdown of comparisons of RDBMS on wikipediate here.

Dealing with installs is one reason to use things like Access and MS SQL Express edition. However if you want to avoid much of the installs, you can use Microsoft SQL Server Compact Edition.

Regardless if I am working with 1 database or 10, I don't like keeping databases locally, I prefer servers that everyone works from.

Blobs

In my opinion the only good way to handle them is with Parameters. Some providers allow you to do things to put the data to strings, but it doesn't appear to be standard.

SharpEditor vs Visual Studio

I don't have any opinion on which is better, because I have never used SharpEditor.

Bound vs Unbound controls and network traffic

Bound controls don't require an open connection, for example, if I use the following code to open a datareader

cmd.ExecuteReader(CommandBehavior.CloseConnection);

then bind it to a DataGridView (windows control), GridView, or DataGrid (ASP.NET controls) it will bind without issue, and continue to work even though the connection to the database is gone.

Connected datatables and datasets are another story, those are what I suspect you are talking about, and those are going to have the same network traffic all the time regardless of provider.


GeneralRe: thanks for sharing ...memberAlaric Dailey8 Feb '10 - 3:42 
You may find my new article of use
 
Load Any Object From Most Any Database
GeneralGood article but the code is too coupledmemberPuchko Vasili24 Jan '10 - 9:54 
IMO it's much more better to use a bit different approach for implementing authentication with less coupling - create a base interface for password providers with methods for generating hashes of passwords, create an implementation of that interface and split other code in classes that will not violate Single Responsibility Principle.
And your database access code, IMO, is a bit too oldschool.
GeneralRe: Good article but the code is too coupledmemberAlaric Dailey24 Jan '10 - 11:36 
I wasn't trying to show any particular design principle, just trying to make the code simple. All the code and information presented, is to keep the code simple. That is why I didn't get into length or complexity checking, design patterns, enabling and disabling the buttons, or anything else.
GeneralRe: Good article but the code is too coupledmemberPuchko Vasili27 Jan '10 - 8:09 
I'm sorry, but decoupled is simple, your current code is more like a spaghetti code (all is mixed in one place). This code works when you need to proof some concepts. But, IMO, it's not suitable for article. Think about young developers that read your article and don't show them a bad example. It's very easy to move code to separate classes that have single responsibility. And the code will become much more readable and simple.
GeneralRe: Good article but the code is too coupledmemberAlaric Dailey27 Jan '10 - 13:31 
I guess we will agree to disagree.
 
Yes it is tightly coupled.
 
No it is not spaghetti code.
 
The code is simple, however not useless.
 
All to often articles, blog entries, books and other examples get caught up in showing everything at once, and coders looking for a few lines of "how-to" code end up looking through pages and pages of someones idea of "proper" code. The situation gets compounded when you add cryptography in the mix. Once some poor programmer finds what they are looking for, they end up spending a good deal of time sifting through the articles and vocabulary they don't understand hoping to glean enough information to make the code work without sacrificing security. Then once they have the code, they pour over the code having to undo the authors ideas, so they can retro-fit it into some project that they had thrust upon them.
 
Consequently this article and the accompanying code could have been HUGE, with explanations of
  • how hashes work
  • polymorphism
  • inheritance

  • abstraction
  • encapsulation
  • random vs pseudo random numbers
  • why use binary fields rather than strings
  • Base64 encoding, and how to use it if you can't use binary fields
  • the "factory" pattern I am taking advantage of in the DB class
  • Connection Pooling
  • genericizing DB code by using the base classes for the parameters
  • genericizing SQL by wrapping the keywords based on the DB information
  • abstracting classes to enable a simple class change to change the login logic
  • separation of interface from logic
  • minimum password length, and why its important
  • password expiration, and why it is a good or bad idea
  • preventing password reuse
  • database normalization
  • foreign keys
 
All of those are great and could have been added, and are covered in great detail in other places. However, not a single one of those explanations, or the required code to show it, is necessary.
 
The idea of this article and the code is to "simply simplify". Not a single line of code more than is necessary, not for abstraction, code reuse or anything else.
GeneralRe: Good article but the code is too coupledmembertiffstormy28 Jan '10 - 4:46 
I don't see this as spaghetti code. The definition of spaghetti code is the following:
 
"Spaghetti code is a pejorative term for source code which has a complex and tangled control structure, especially one using many GOTOs, exceptions, threads, or other "unstructured" branching constructs."
 
When I am looking for an answer to a particiluar question, I don't want to have to sift through somebody else's idea of what they think is "good coding practice". Many places have different ways of doing things. I just want to find what I need and plug it in to the structure I am currently working with.
GeneralNice articlemvpRichard MacCutchan24 Jan '10 - 6:28 
A nicely structured article with good descriptions of the algorithms in use. However, I would like to see a few more comments in or around the code as I, and probably others, have fairly limited knowledge of SQL/DataBase.
 
MVP 2010 - are they mad?

GeneralRe: Nice articlememberAlaric Dailey24 Jan '10 - 7:07 
Thanks for the feedback.
 
What would you like explained?
 
This article is supposed to be as simple as possible, I am disappointed that I left any confusion.
GeneralRe: Nice articlememberSledgeHammer0124 Jan '10 - 8:20 
I don't understand Richards complaint either. This article is not about databases or how to store passwords in them. Its more of a high level "best practices" type article for storing passwords in general. The fact that you included some DB code is icing on the cake. I gave you a 5.
GeneralRe: Nice articlemvpRichard MacCutchan24 Jan '10 - 9:24 
SledgeHammer01 is mistaken, I am not complaining. I just noted that as a non-Database programmer, I found some of the code pertaining to databases difficult to understand. That does not detract from my view that this is a very good article. It's just that I (being a simple soul) do not understand the SQL stuff.
 
MVP 2010 - are they mad?

GeneralRe: Nice articlememberAlaric Dailey24 Jan '10 - 11:42 
Perhaps my article on getting information from the .NET DataProviders might help.
 
Using Information from the .NET DataProvider
 
Honestly though, I would be happy to document the code better, to help others as well as yourself. Just let me know what is confusing you.
GeneralRe: Nice articlemvpRichard MacCutchan24 Jan '10 - 22:03 
I think this discussion is going too far off track. Maybe a better solution would be for me to study the DB stuff in greater detail to understand how it works. I really did not want this to detract from my view that all the information on password handling is really useful, and very well presented.
 
MVP 2010 - are they mad?

GeneralRe: Nice articlememberAlaric Dailey1 Feb '10 - 7:47 
Perhaps my new article is what you are looking for Smile | :)
 
Don't hard code your DataProviders
GeneralRe: Nice articlemvpRichard MacCutchan1 Feb '10 - 9:10 
Thanks,
I'll take a look.
 
MVP 2010 - are they mad?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 25 Jan 2010
Article Copyright 2010 by Alaric Dailey
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid