2007-08-21

More secure passwords

Storing a plain-text password in a DB leaves your system open to abuse from anybody with access to the tables (sys admin for example). Here is the technique I recently used. Instead of storing the password itself I store a hash of the password, and a random "salt" that was used to create the hash to make it less predictable.


[UmlTaggedValue("Eco.Length", "255")]
private string PasswordHash
{
get
{
...
}
set
{
...
}
}

[UmlTaggedValue("Eco.AllowNULL", "True")]
[UmlTaggedValue("Eco.Length", "40")]
private string PasswordSalt
{
get
{
...
}
set
{
...
}
}



As you can see I do not store the password, just a hash + salt, both of
which are private. Here are the methods used to set the values of these properties.

public void SetPassword(string newPassword)
{
if (newPassword == null)
throw new ArgumentNullException("password");
if (newPassword.Length < 6)
throw new ArgumentException("Password must be at least 6 characters");

PasswordSalt = Guid.NewGuid().ToString();
PasswordHash = EncryptPassword(newPassword);
}

private string EncryptPassword(string password)
{
TripleDESCryptoServiceProvider des;
MD5CryptoServiceProvider hashmd5;
byte[] pwdhash, buff;
hashmd5 = new MD5CryptoServiceProvider();
pwdhash = hashmd5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(PasswordSalt));
hashmd5 = null;
des = new TripleDESCryptoServiceProvider();
des.Key = pwdhash;
des.Mode = CipherMode.ECB; //CBC, CFB
buff = ASCIIEncoding.ASCII.GetBytes(password);
string result =
Convert.ToBase64String(
des.CreateEncryptor().TransformFinalBlock(
buff, 0, buff.Length)
);
return result;
}


Now the code required to get a user by ID/Password

public static User FindUserByUserIDAndPassword(IEcoServiceProvider serviceProvider, 
string userID, string password)
{
string query =
string.Format("->select(userID.sqlLikeCaseInsensitive('{0}'))",
BusinessClassesHelper.EscapeOcl(userID));

User user =
BusinessClassesHelper.SelectFirstObject(serviceProvider, "User", query);

if (user != null && user.EncryptPassword(password) == user.PasswordHash)
return user;
return null;
}


The BusinessClassesHelper is a class with static methods to help with OCL operations, such as escaping user input values to avoid OCL injection.

using System;
using System.Collections.Generic;
using System.Text;
using Eco.ObjectRepresentation;
using Eco.Services;
using Eco.Handles;

namespace xxxxxxxxxxxxxxx
{
public static class BusinessClassesHelper
{
public static string EscapeOcl(string ocl)
{
if (string.IsNullOrEmpty(ocl))
throw new ArgumentNullException("ocl");

ocl = ocl.Replace("\\", "\\\\");
ocl = ocl.Replace("'", "\\'");
return ocl;
}

public static T SelectFirstObject(IEcoServiceProvider serviceProvider,
string className, string criteria)
{
if (serviceProvider == null)
throw new ArgumentNullException("serviceProvider");
if (string.IsNullOrEmpty(className))
throw new ArgumentNullException("className");
if (criteria == null)
throw new ArgumentNullException("criteria");

IOclPsService psService =
EcoServiceHelper.GetOclPsService(serviceProvider);
IOclService oclService =
EcoServiceHelper.GetOclService(serviceProvider);

IObjectList result =
psService.Execute(className + ".allInstances" + criteria);
result =
oclService.Evaluate(className + ".allLoadedObjects" + criteria) as IObjectList;
if (result.Count == 0)
return default(T);
else
return (T)result[0].AsObject;
}
}
}

No comments: