Étiquettes

, , , , , , , , , , , , ,

ASP.NET membership is designed to enable you to easily use a number of different membership providers for your ASP.NET applications. You can use the supplied membership providers that are included with the .NET Framework, or you can implement your own providers.

There are two primary reasons for creating a custom membership provider.

  • You need to store membership information in a data source that is not supported by the membership providers included with the .NET Framework, such as a MysQL database, an Oracle database, or other data sources.
  • You need to manage membership information using a database schema that is different from the database schema used by the providers that ship with the .NET Framework. A common example of this would be membership data that already exists in a SQL Server database for a company or Web site.

This tutorial  is a continuation of our tutorial  How to configure Custom Membership and Role Provider using ASP.NET MVC4

Let’s talk about password encoding and decoding and take the opportunity to introduce the Unit Of Work Pattern used to insert and retrieve data.

There exists a sequence of hash functions SHA-0, SHA-1, SHA-2 and the most recent SHA-3.

SHA-3, is a new cryptographic hash function that has been selected by NIST in October 2012 following a public competition launched in 2007, this because the weaknesses discovered on MD-5 and SHA-1 let fear fragility SHA-2 is built on the same schéma. It has variations that can produce hashes 224, 256, 384 and 512 bits. It is built on a different principle from that of MD5, SHA-1 and SHA-2 functions.

So in our tutorial, we will use the SHA-3  512 bits,

For this, it is not intended to replace SHA-2, which is at present not been compromised by a significant attack, but to provide an alternative response to attacks against MD5 possibilities standards SHA-0 and SHA-1.

A. Unit Of Work Pattern

The purpose of this article is not an introduction to Unit Of Work Pattern but a particular use of it. So for more information Unit Of Work Pattern, please read articles that deal with this subject such as http://msdn.microsoft.com/en-us/magazine/dd882510.aspx

  • Create a class library Project and reference EntityFramwork.dll
  • Generate the model and classes from membeship database (Model.Context.tt, Model.Entities.tt and Model.mapping.tt ) . If you are newly in Entity Framwork, please read our tutorial  Introduction to entity framework Code first
  • Create IRepository interface

public interface IRepository<T> where T : class
{
/// <summary>
/// Get the total objects count.
/// </summary>
int Count { get; }

/// <summary>
/// Gets all objects from database
/// </summary>
IQueryable<T> All();

/// <summary>
/// Gets object by primary key.
/// </summary>
/// <param name= »id »> primary key </param>
/// <returns> </returns>
T GetById(object id);

/// <summary>
/// Gets objects via optional filter, sort order, and includes
/// </summary>
/// <param name= »filter »> </param>
/// <param name= »orderBy »> </param>
/// <param name= »includeProperties »> </param>
/// <returns> </returns>
IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = «  »);

/// <summary>
/// Gets objects from database by filter.
/// </summary>
/// <param name= »predicate »> Specified a filter </param>
IQueryable<T> Filter(Expression<Func<T, bool>> predicate);

/// <summary>
/// Gets objects from database with filting and paging.
/// </summary>
/// <param name= »filter »> Specified a filter </param>
/// <param name= »total »> Returns the total records count of the filter. </param>
/// <param name= »index »> Specified the page index. </param>
/// <param name= »size »> Specified the page size </param>
IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50);

/// <summary>
/// Gets the object(s) is exists in database by specified filter.
/// </summary>
/// <param name= »predicate »> Specified the filter expression </param>
bool Contains(Expression<Func<T, bool>> predicate);

/// <summary>
/// Find object by keys.
/// </summary>
/// <param name= »keys »> Specified the search keys. </param>
T Find(params object[] keys);

/// <summary>
/// Find object by specified expression.
/// </summary>
/// <param name= »predicate »> </param>
T Find(Expression<Func<T, bool>> predicate);

/// <summary>
/// Create a new object to database.
/// </summary>
/// <param name= »entity »> Specified a new object to create. </param>
T Create(T entity);

/// <summary>
/// Deletes the object by primary key
/// </summary>
/// <param name= »id »> </param>
void Delete(object id);

/// <summary>
/// Delete the object from database.
/// </summary>
/// <param name= »entity »> Specified a existing object to delete. </param>
void Delete(T entity);

/// <summary>
/// Delete objects from database by specified filter expression.
/// </summary>
/// <param name= »predicate »> </param>
void Delete(Expression<Func<T, bool>> predicate);

/// <summary>
/// Update object changes and save to database.
/// </summary>
/// <param name= »entity »> Specified the object to save. </param>
void Update(T entity);
/// <summary>
///
/// </summary>
/// <param name= »query »></param>
/// <param name= »parameters »></param>
/// <returns></returns>
IEnumerable<T> GetWithRawSql(string query, params object[] parameters);
}

  • Next, we are going to implement the IRepository interface

public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext _dbContext;
protected readonly DbSet<T> _dbSet;

public Repository(DbContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}

public virtual int Count
{
get { return _dbSet.Count(); }
}

public virtual IQueryable<T> All()
{
return _dbSet.AsQueryable();
}

public virtual T GetById(object id)
{
return _dbSet.Find(id);
}

public virtual IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = «  »)
{
IQueryable<T> query = _dbSet;

if (filter != null)
{
query = query.Where(filter);
}

if (!String.IsNullOrWhiteSpace(includeProperties))
{
foreach (var includeProperty in includeProperties.Split(new[] { ‘,’ }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}

if (orderBy != null)
{
return orderBy(query).AsQueryable();
}
else
{
return query.AsQueryable();
}
}

public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate)
{
return _dbSet.Where(predicate).AsQueryable();
}

public virtual IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50)
{
int skipCount = index * size;
var resetSet = filter != null ? _dbSet.Where(filter).AsQueryable() : _dbSet.AsQueryable();
resetSet = skipCount == 0 ? resetSet.Take(size) : resetSet.Skip(skipCount).Take(size);
total = resetSet.Count();
return resetSet.AsQueryable();
}

public bool Contains(Expression<Func<T, bool>> predicate)
{
return _dbSet.Count(predicate) > 0;
}

public virtual T Find(params object[] keys)
{
return _dbSet.Find(keys);
}

public virtual T Find(Expression<Func<T, bool>> predicate)
{
return _dbSet.FirstOrDefault(predicate);
}

public virtual T Create(T entity)
{
var newEntry = _dbSet.Add(entity);
return newEntry;
}

public virtual void Delete(object id)
{
var entityToDelete = _dbSet.Find(id);
Delete(entityToDelete);
}

public virtual void Delete(T entity)
{
if (_dbContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}

public virtual void Delete(Expression<Func<T, bool>> predicate)
{
var entitiesToDelete = Filter(predicate);
foreach (var entity in entitiesToDelete)
{
if (_dbContext.Entry(entity).State == EntityState.Detached)
{
_dbSet.Attach(entity);
}
_dbSet.Remove(entity);
}
}

public virtual void Update(T entity)
{
var entry = _dbContext.Entry(entity);
_dbSet.Attach(entity);
entry.State = EntityState.Modified;
}

public virtual IEnumerable<T> GetWithRawSql(string query, params object[] parameters)
{
return _dbSet.SqlQuery(query, parameters).ToList();
}
}

  • Lets create a IDbContextFactory interface  interface and implement it

Hash1png

We will use the DBContextFactory interface to handle multiple database or schemas, for example if a database contains multiple schemas, we will have the opportunity to work with multiple schemas within a single context.

  • Lets create a IUnitOfWork interfaceand implement it

Hash2

IUnitOfWork  interface will be used only to get Repositories, save context and finally  dispose objectsHash3

B. Implementation of MembershipProvider

Our CustomMembershipProvider derives from  MembershipProvider

The first thing we are doing is to override Initialize(string name, NameValueCollection config) so as to get config parameters and also get ApplicationId or create it if does not exist

Hash5

Try it :

public override void Initialize(string name, NameValueCollection config)
{
// Initialize values from web.config.
if (config == null)
throw new ArgumentNullException(« config »);

// Initialize the abstract base class.
base.Initialize(name, config);

// Get application name
if (config[« applicationName »] == null || config[« applicationName »].Trim() == «  »)
{
ApplicationName = System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath;
}
else
{
ApplicationName = config[« applicationName »];
}

// Verify a record exists in the application table.
if ((_applicationId == Guid.Empty) || String.IsNullOrEmpty(_applicationName))
{
// Insert record for application.
if (_applicationId == Guid.Empty)
{
_applicationId = new MemberShipService().GetApplicationId(Guid.NewGuid(), _applicationName, String.Empty);
}
}
}

B.1  Encoding Password

Now lets create a random salt using SHA 512 string format CreateSalt512

In cryptography, a salt is random data that are used as an additional input to a one-way function that hashes a password or passphrase. The primary function of salts is to defend against dictionary attacks and pre-computed rainbow table attacks.

A new salt is randomly generated for each password. In a typical setting, the salt and the password are concatenated and processed with a cryptographic hash function, and the resulting output (but not the original password) is stored with the salt in a database. Hashing allows for later authentication while defending against compromise of the plaintext password in the event that the database is somehow compromised.

Hash6

Try it :

public static string RandomString(int size, bool lowerCase)
{
var builder = new StringBuilder();
var random = new Random();
for (int i = 0; i < size; i++)
{
char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
builder.Append(ch);
}
return lowerCase ? builder.ToString().ToLower() : builder.ToString();
}
private static string CreateSalt512()
{
var message = RandomString(512,false);
return BitConverter.ToString((new SHA512Managed()).ComputeHash(Encoding.ASCII.GetBytes(message))).Replace(« -« , «  »);
}

  • Now we will generate a hashed Password using our clear password and our resultes salt (secret key)

Hash7

Try it :

private string GenerateHMAC(string clearMessage, string secretKeyString)
{
var encoder = new ASCIIEncoding();
var messageBytes = encoder.GetBytes(clearMessage);
var secretKeyBytes = new byte[secretKeyString.Length / 2];
for (int index = 0; index < secretKeyBytes.Length; index++)
{
string byteValue = secretKeyString.Substring(index * 2, 2);
secretKeyBytes[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
}
var hmacsha512 = new HMACSHA512(secretKeyBytes);

byte[] hashValue = hmacsha512.ComputeHash(messageBytes);

string hmac = «  »;
foreach (byte x in hashValue)
{
hmac += String.Format(« {0:x2} », x);
}

return hmac.ToUpper();
}

  • Now we ‘ll use the GenerateHMAC function to Hash password and put in on database table

Hash8

Try it :

public override MembershipUser GetUser(string username, bool userIsOnline)
{
// Please implement it : check if user exist or not on database
return null;
}

public override MembershipUser CreateUser(string username,string password,string email,string passwordQuestion,string passwordAnswer,
bool isApproved,
object providerUserKey,
out MembershipCreateStatus status)
{
status = MembershipCreateStatus.Success;

MembershipUser u = GetUser(username, false);

if (u == null)
{
DateTime createDate = DateTime.UtcNow;
string salt = CreateSalt512();

var user = new aspnet_Users
{
UserId = new Guid(providerUserKey.ToString()),UserName = username,LoweredUserName = username.ToLowerInvariant(),ApplicationId = _applicationId,
IsAnonymous = false,
LastActivityDate = createDate,
MobileAlias = null,

aspnet_Membership = new aspnet_Membership
{
ApplicationId = _applicationId,Comment = null,CreateDate = createDate,Email = email,IsApproved = isApproved,IsLockedOut = false,
LastLoginDate = createDate,FailedPasswordAnswerAttemptCount = 0,LastLockoutDate = _minDate,
LoweredEmail = (email != null ? email.ToLowerInvariant() : null),
MobilePIN = null,Password = GenerateHMAC(password, salt),FailedPasswordAnswerAttemptWindowStart = _minDate,
FailedPasswordAttemptCount = 0,FailedPasswordAttemptWindowStart = _minDate,LastPasswordChangedDate = createDate,
PasswordSalt = salt,PasswordFormat = (int)MembershipPasswordFormat.Hashed,PasswordQuestion = passwordQuestion
}

};

new MemberShipService().CreateUser(user);
return GetUser(username, false);
}
status = MembershipCreateStatus.DuplicateUserName;
return null;
}

  • Now lets implement  our operations  of  MemberShipService class :

Hash9

Try it :

public Guid GetApplicationId(Guid id, string applicationName, string description)
{
using (var unitOfWork = new UnitOfWork(new DbContextFactory<LogCornerSecurityContext>()))
{
// Returns Application id exist
string loweredApplicationName = applicationName.ToLowerInvariant();
var result = unitOfWork.GetRepository<aspnet_Applications>()
.Get(a => a.LoweredApplicationName == loweredApplicationName).FirstOrDefault();

// Create it if does not exist
if (result == null)
{

result = unitOfWork.GetRepository<aspnet_Applications>().Create(new aspnet_Applications
{
ApplicationId = id,
ApplicationName = applicationName,
Description = description,
LoweredApplicationName = applicationName.ToLowerInvariant()
});
unitOfWork.Save();
}
return result.ApplicationId;
}
}

public void CreateUser(aspnet_Users user)
{
using (var unitOfWork = new UnitOfWork(new DbContextFactory<LogCornerSecurityContext>()))
{
unitOfWork.GetRepository<aspnet_Users>().Create(user);
unitOfWork.Save();
}
}

  •  Run our sample application and enter user informations, so password is hashed and stored as follows :

Login informations

Hash10

Runtime values

Hash11

Database table [dbo].[aspnet_Membership] valuesHash12

So we can see that password is hashed according to salt and stored in database.

To better understand and right implement salt, please seek for more information about How Hashes are Cracked ?  what happen if hacker try to decode password ?

Before using Salt Password Hashing, we must understand what is Dictionary and Brute Force Attacks, Reverse Lookup Tables, Lookup Tables and Rainbow Tables.

B.2  << Matching >> Password

Now, to << decode >> the password (if one can speak of decoding) we can simply hash the password provided by the user with the basic salt stored on database and compare the resulting hash with the hash stored in the database.

So one and only the user knows the password. If any one else can discover the password then we are the victim of a successful attack.

The validate function can look like this

Untitled pictureTry it :

public override bool ValidateUser(string username, string password)
{
// Get the user so as to find Salt and Hashed Password
aspnet_Users user = new MemberShipService().GetUser(username, _applicationId);

if (user != null)
{
// hash the password provided by the user with the basic salt stored on database and compare
// the resulting hash with the hash stored in the database

bool isAuthenticated = (user.aspnet_Membership.Password == GenerateHMAC(password, user.aspnet_Membership.PasswordSalt));
if (isAuthenticated)
{
// If success, then update datetime login
user.LastActivityDate = DateTime.Now;
user.aspnet_Membership.LastLoginDate = DateTime.Now;
UpdateUser(user);
}
else
{
// If not logged then update failure account so as to lock user
UpdateFailureCount(username, « password », isAuthenticated);
}
return isAuthenticated;
}
return false;
}

Now, what can happen if clear password travels on networks before being encoded ?

We will see this topic at next tutorial

Regards