不使用membership的api怎么使用现有的数据库,模拟登陆?

xiaoqiushui 2008-04-01 11:59:29
原有数据库是使用asp.net 2.0自带的一套表,用户的密码似乎是二次加密产生的。
web.config中的相关配置如下:
enablePasswordRetrieval="false"
passwordFormat="Hashed"

请问,我如何使用现在的数据库,利用其他脚本比如:jsp,php来实现登陆验证。
因为用户的密码获得不回来。
...全文
460 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
已经结贴了,怎么还看不到分?
怫悰 2008-04-02
  • 打赏
  • 举报
回复
不过为什么要转用其它的呢?
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
wangfuchong已经在另外的帖子得分了。
这里就没有分了。
谢谢各位支持!
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
哈,大家还真是热情啊,我还发了一个帖子,在另外一个论坛,去那里冒一下泡吧,我把那里的分也散了。
http://topic.csdn.net/u/20080401/11/26ae10a7-b4b7-47e0-af22-aeb753de7a70.html
怫悰 2008-04-02
  • 打赏
  • 举报
回复
  其中我们可以看到两个比较令人感兴趣的方法:GenerateSalt和EncodePassword。由于本文讨论的仅仅是密码的散列,而不是整个用户注册过程,所以这里只对这两个函数进行分析。

  这两个方法来自于SqlMembershipProvider的父类,MembershipProvider。

GenerateSalt方法的代码比较简单:

GenerateSalt
internal string GenerateSalt()
{
byte[] data = new byte[0x10];
new RNGCryptoServiceProvider().GetBytes(data);
return Convert.ToBase64String(data);
}

  但是要注意的是,在这种方法里Salt值的高度随机性是安全的保障,所以不能简单的使用Random来获取随机数,而应该使用更安全的方式。这里使用了RNGCryptoServiceProvider来生成随机数。

EncodePassword方法的代码也不难:

EncodePassword
internal string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0)
{
return pass;
}
// 将密码和salt值转换成字节形式并连接起来
byte[] bytes = Encoding.Unicode.GetBytes(pass);
byte[] src = Convert.FromBase64String(salt);
byte[] dst = new byte[src.Length + bytes.Length];
byte[] inArray = null;
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
// 选择算法,对连接后的值进行散列
if (passwordFormat == 1)
{
 HashAlgorithm algorithm = HashAlgorithm.Create(Membership.HashAlgorithmType);
 if ((algorithm == null) && Membership.IsHashAlgorithmFromMembershipConfig)
 {
 RuntimeConfig.GetAppConfig().Membership.ThrowHashAlgorithmException();
 }
inArray = algorithm.ComputeHash(dst);
}
else
{
inArray = this.EncryptPassword(dst);
}
// 以字符串形式返回散列值
return Convert.ToBase64String(inArray);
}

  这段代码的作用就是,首先将密码和salt值转换成字节数组(分别放到bytes和src数组中),然后拼接到一起(dst数组)。之后再根据Web.config中设置的加密算法,对这个拼接值进行散列,最后把散列值转换成字符串形式返回。

  最后,用户登录时,将会使用SqlMembershipProvider的CheckPassword方法对密码进行检验。该方法有两种重载形式,最为完整的一种如下所示:

CheckPassword
private bool CheckPassword(string username, string password, bool updateLastLoginActivityDate, bool failIfNotApproved, out string salt, out int passwordFormat)
{
SqlConnectionHolder connection = null;
string str; // 密码散列值
int num;
int num2;
int num3;
bool flag2;
DateTime time;
DateTime time2;
// 从数据库中拿到Hash和Salt
this.GetPasswordWithFormat(username, updateLastLoginActivityDate, out num, out str, out passwordFormat, out salt, out num2, out num3, out flag2, out time, out time2);
if (num != 0)
{
return false;
}
if (!flag2 && failIfNotApproved)
{
return false;
}
// 对用户刚刚输入的密码进行散列
string str2 = base.EncodePassword(password, passwordFormat, salt);

// 比较两个散列值,看密码是否相等
bool objValue = str.Equals(str2);
if ((objValue && (num2 == 0)) && (num3 == 0))
{
return true;
}
try
{
 try
 {
 connection = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);
this.CheckSchemaVersion(connection.Connection);
 SqlCommand command = new SqlCommand("dbo.aspnet_Membership_UpdateUserInfo", connection.Connection);
DateTime utcNow = DateTime.UtcNow;
command.CommandTimeout = this.CommandTimeout;
command.CommandType = CommandType.StoredProcedure;
 command.Parameters.Add(this.CreateInputParam("@ApplicationName", SqlDbType.NVarChar, this.ApplicationName));
command.Parameters.Add(this.CreateInputParam("@UserName", SqlDbType.NVarChar, username));
command.Parameters.Add(this.CreateInputParam("@IsPasswordCorrect", SqlDbType.Bit, objValue));

 command.Parameters.Add(this.CreateInputParam("@UpdateLastLoginActivityDate", SqlDbType.Bit, updateLastLoginActivityDate));
command.Parameters.Add(this.CreateInputParam("@MaxInvalidPasswordAttempts", SqlDbType.Int, this.MaxInvalidPasswordAttempts));
command.Parameters.Add(this.CreateInputParam("@PasswordAttemptWindow", SqlDbType.Int, this.PasswordAttemptWindow));
 command.Parameters.Add(this.CreateInputParam("@CurrentTimeUtc", SqlDbType.DateTime, utcNow));
 command.Parameters.Add(this.CreateInputParam("@LastLoginDate", SqlDbType.DateTime, objValue ? utcNow : time));
  command.Parameters.Add(this.CreateInputParam("@LastActivityDate", SqlDbType.DateTime, objValue ? utcNow : time2));
 SqlParameter parameter = new SqlParameter("@ReturnValue", SqlDbType.Int);
 parameter.Direction = ParameterDirection.ReturnValue;
 command.Parameters.Add(parameter);
 command.ExecuteNonQuery();
 num = (parameter.Value != null) ? ((int) parameter.Value) : -1;
return objValue;
}
finally
{
if (connection != null)
{
connection.Close();
connection = null;
}
}
}
catch
{
throw;
}
return objValue;
}

  这个代码首先通过GetPasswordWithFormat得到了Hash值(变量str)和Salt值(变量salt),然后对用户输入的密码(参数password)进行与注册时一样的散列(只是salt值使用了数据库中现存的值)得到散列值str2,之后通过对比str和str2,就知道密码正确与否了。

  4 小结

  本文只是简单地介绍了加Salt散列的工作方式(而非原理)、ASP.NET 在Membership中对其的实现。通过本文大家虽然无法对加Salt加密的有点和原理“知其所以然”,但相信大家应该大致了解了这种方式的使用方法,并能通过修改Membership的代码实现自己的密码散列存储了。

  由于时间有限,Anders Liu这篇文章写得很潦草,罗列了不少代码却没有系统性介绍,还望大家原谅。下一篇文章我将相对完整地介绍如何实现自己的用户登录(无需使用MembershipProvider,但同时也丧失了Login等控件为我们带来的便利)。
怫悰 2008-04-02
  • 打赏
  • 举报
回复
CreateUser
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
{
string str3;
MembershipUser user;
if (!SecUtility.ValidateParameter(ref password, true, true, false, 0x80))
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
// 生成salt值
string salt = base.GenerateSalt();
// 结合salt值对密码进行散列
string objValue = base.EncodePassword(password, (int) this._PasswordFormat, salt);
if (objValue.Length > 0x80)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if (passwordAnswer != null)
{
passwordAnswer = passwordAnswer.Trim();
}
if (!string.IsNullOrEmpty(passwordAnswer))
{
 if (passwordAnswer.Length > 0x80)
  {
  status = MembershipCreateStatus.InvalidAnswer;
  return null;
}
  str3 = base.EncodePassword(passwordAnswer.ToLower(CultureInfo.InvariantCulture), (int) this._PasswordFormat, salt);
}
else
{
str3 = passwordAnswer;
}
if (!SecUtility.ValidateParameter(ref str3, this.RequiresQuestionAndAnswer, true, false, 0x80))
{
status = MembershipCreateStatus.InvalidAnswer;
return null;
}
 if (!SecUtility.ValidateParameter(ref username, true, true, true, 0x100))
{
status = MembershipCreateStatus.InvalidUserName;
return null;
}
if (!SecUtility.ValidateParameter(ref email, this.RequiresUniqueEmail, this.RequiresUniqueEmail, false, 0x100))
{
status = MembershipCreateStatus.InvalidEmail;
return null;
}
 if (!SecUtility.ValidateParameter(ref passwordQuestion, this.RequiresQuestionAndAnswer, true, false, 0x100))
{
status = MembershipCreateStatus.InvalidQuestion;
return null;
}
if ((providerUserKey != null) && !(providerUserKey is Guid))
{
status = MembershipCreateStatus.InvalidProviderUserKey;
return null;
}
if (password.Length < this.MinRequiredPasswordLength)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
int num = 0;
for (int i = 0; i <password.Length; i++)
{
if (!char.IsLetterOrDigit(password, i))
{
num++;
}
}
if (num < this.MinRequiredNonAlphanumericCharacters)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
if ((this.PasswordStrengthRegularExpression.Length > 0) && !Regex.IsMatch(password, this.PasswordStrengthRegularExpression))
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
ValidatePasswordEventArgs e = new ValidatePasswordEventArgs(username, password, true);
this.OnValidatingPassword(e);
if (e.Cancel)
{
status = MembershipCreateStatus.InvalidPassword;
return null;
}
try
{
SqlConnectionHolder connection = null;
try
{
  connection = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);
 this.CheckSchemaVersion(connection.Connection);
 DateTime time = this.RoundToSeconds(DateTime.UtcNow);
 SqlCommand command = new SqlCommand("dbo.aspnet_Membership_CreateUser", connection.Connection);
 command.CommandTimeout = this.CommandTimeout;
 command.CommandType = CommandType.StoredProcedure;
 command.Parameters.Add(this.CreateInputParam("@ApplicationName", SqlDbType.NVarChar, this.ApplicationName));
 command.Parameters.Add(this.CreateInputParam("@UserName", SqlDbType.NVarChar, username));
 command.Parameters.Add(this.CreateInputParam("@Password", SqlDbType.NVarChar, objValue));
 command.Parameters.Add(this.CreateInputParam("@PasswordSalt", SqlDbType.NVarChar, salt));
 command.Parameters.Add(this.CreateInputParam("@Email", SqlDbType.NVarChar, email));
 command.Parameters.Add(this.CreateInputParam("@PasswordQuestion", SqlDbType.NVarChar, passwordQuestion));
 command.Parameters.Add(this.CreateInputParam("@PasswordAnswer", SqlDbType.NVarChar, str3));
 command.Parameters.Add(this.CreateInputParam("@IsApproved", SqlDbType.Bit, isApproved));

 command.Parameters.Add(this.CreateInputParam("@UniqueEmail", SqlDbType.Int, this.RequiresUniqueEmail ? 1 : 0));
 command.Parameters.Add(this.CreateInputParam("@PasswordFormat", SqlDbType.Int, (int) this.PasswordFormat));
 command.Parameters.Add(this.CreateInputParam("@CurrentTimeUtc", SqlDbType.DateTime, time));
 SqlParameter parameter = this.CreateInputParam("@UserId", SqlDbType.UniqueIdentifier, providerUserKey);
parameter.Direction = ParameterDirection.InputOutput;
command.Parameters.Add(parameter);
parameter = new SqlParameter("@ReturnValue", SqlDbType.Int);
parameter.Direction = ParameterDirection.ReturnValue;
command.Parameters.Add(parameter);
command.ExecuteNonQuery();
int num3 = (parameter.Value != null) ? ((int) parameter.Value) : -1;
if ((num3 < 0) || (num3 > 11))
{
num3 = 11;
}
status = (MembershipCreateStatus) num3;
if (num3 != 0)
{
return null;
}
 providerUserKey = new Guid(command.Parameters["@UserId"].Value.ToString());
 time = time.ToLocalTime();
 user = new MembershipUser(this.Name, username, providerUserKey, email, passwordQuestion, null, isApproved, false, time, time, time, time, new DateTime(0x6da, 1, 1));
}
finally
{
if (connection != null)
{
connection.Close();
connection = null;
}
}
}
catch
{
throw;
}
return user;
}

怫悰 2008-04-02
  • 打赏
  • 举报
回复
冒泡接分

hash过的密码好像是不能找回来的吧?找回来的是否是新密码?

只有了解默认或者原来网站使用的membershipprovider中的具体加密过程,然后用jsp或php实现(不懂,但是应该能找到相关方法代码吧)

转来的:(更多自己搜一搜看有没有)

浅析ASP.NET 2.0的用户密码加密机制

  摘要:

  1 加Salt散列
  2 ASP.NET 2.0 Membership中与密码散列有关的代码

  声明:本文所罗列之源代码均通过Reflector取自.NET Framework类库,Anders Liu引用这些代码仅出于学习和研究的目的。

  前一段关于密码的存储问题产生了一些讨论。我所看到的景象是,首先在cnbeta新 闻中提到中国某银行将强制冻结密码过于简单(如6个8)的帐户,引发了争论。一方认为银行采用明文存放用户密码;另一方则认为,即便密码是经过散列存放的,但只要得到“6个8”的散列值,通过对比散列值也可以发现具有特定密码的用户。



  后来在博客园(cnblogs.com)也看到有朋友发帖讨论了密码的散列存储,再后来在MVP的QQ群里也就这个问题小小议论了一番。

  其实,对密码进行散列存储不是一个新鲜话题了,解决起来也不是很难,但很多人还是不大了解。Anders Liu这个小文只是强调一下“加Salt散列”这个简单的技术,并给出ASP.NET Membership所使用的代码。

  本来打算写一篇介绍如何实现用户登录功能的文章的,但因为时间有限,所以先介绍一下密码的散列,下一篇再介绍用户登录。
  1 密码必须散列存储

  (内容略)

  2 加Salt散列

  我们知道,如果直接对密码进行散列,那么黑客(统称那些有能力窃取用户数据并企图得到用户密码的人)可以对一个已知密码进行散列,然后通过对比散列值得到某用户的密码。换句话说,虽然黑客不能取得某特定用户的密码,但他可以知道使用特定密码的用户有哪些。

  加Salt可以一定程度上解决这一问题。所谓加Salt,就是加点“佐料”。其基本想法是这样的——当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,已确定密码是否正确。

  这里的“佐料”被称作“Salt值”,这个值是由系统随机生成的,并且只有系统知道。这样,即便两个用户使用了同一个密码,由于系统为它们生成的 salt值不同,他们的散列值也是不同的。即便黑客可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了(密码和salt值都得和黑客使用的一样才行)。

  下面详细介绍一下加Salt散列的过程。介绍之前先强调一点,前面说过,验证密码时要使用和最初散列密码时使用“相同的”佐料。所以Salt值是要存放在数据库里的。

  图1. 用户注册

  如图1所示,注册时,

  1)用户提供密码(以及其他用户信息);
  2)系统为用户生成Salt值;
  3)系统将Salt值和用户密码连接到一起;
  4)对连接后的值进行散列,得到Hash值;
  5)将Hash值和Salt值分别放到数据库中。

  图2. 用户登录

  如图2所示,登录时,

  1)用户提供用户名和密码;
  2)系统通过用户名找到与之对应的Hash值和Salt值;
  3)系统将Salt值和用户提供的密码连接到一起;
  4)对连接后的值进行散列,得到Hash'(注意有个“撇”);
  5)比较Hash和Hash'是否相等,相等则表示密码正确,否则表示密码错误。

  3 ASP.NET 2.0 Membership中的相关代码

  (省略关于Membership的介绍若干字)

  本文Anders Liu仅研究了SqlMembershipProvider,该类位于System.Web.dll,System.Web.Security命名空间中。

  首先,要使用Membership,必须先用aspnet_regsql.exe命令来配置数据库,该工具会向现有数据库中添加一系列表和存储过程等,配置好的数据库中有一个表aspnet_Membership,就是用于存放用户帐户信息的。其中我们所关注的列有三个——Password、 PasswordFormat和PasswordSalt。

  Password存放的是密码的散列值,PasswordFormat存放用于散列密码所使用的算法,PasswordSalt就是系统生成的Salt值了。

  注册时用到了该类的CreateUser方法,该方法主要代码如下:
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
<membership>
<providers>
<remove name="AspNetSqlMembershipProvider"/>
<add connectionStringName="MSSQLServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="10"
minRequiredPasswordLength="6"
passwordAttemptWindow="5"
name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
</providers>
</membership>
我的配置文件象上面的形式,理论上是使用缺省的SHA1
刚才用reflactor看了一下web.dll中关于SqlMembershipProvider的实现,模仿认证登陆是可能的,
不过这只是针对已经存在的用户,如果新注册的用户,还要模拟membership的注册加密过程,这就麻烦了。
没什么太好的办法,目前我做的是在登陆的地方增加了一段代码,把通过认证的用户的密码md5一次,存储到另外的表中,
没登陆的用户设置缺省密码,整个数据库移植到mysql。

其实我的问题是不是应该这样问:“怎样把上面配置的membership的数据库移植到其他平台,如:php+mysql?”
boblaw 2008-04-02
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 xiaoqiushui 的回复:]
你提的另外一种方法似乎也有借鉴意义,可是原有的web.config文件中已经指定了enablePasswordRetrieval="false"
那么通过二次加密之后的密码是不是已经不能获得回来了,即便是有PasswordSalt。
[/Quote]
enablePasswordRetrieval並不代表密碼不能回來。而是根據passwordFormat是否為Hashed來判斷的,如果是,就是使用了不對稱加密,無法解密的。

其實是否能夠還原密碼,並不重要。因為密碼的判斷衹要判斷加密後的數據是否相等即可。因為最重要的是要知道使用了什么加密算法。判斷的方法是:
假設有下面這個配置節:

<membership defaultProvider="SqlProvider"
userIsOnlineTimeWindow="20" hashAlgorithmType="SHA1">
<providers>
<add name="SqlProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="onlineConnectionString"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
passwordFormat="Hashed" />
</providers>
</membership>

1. passwordFormat="Hashed",所以為哈希算法
2. hashAlgorithmType="SHA1",可知算法為SHA1。
3. 如果membership中沒有hashAlgorithmType屬性,那么要以machineKey配置節的validation屬性為準
4. 如果membership配置節不存在,那么系統默認的hashed算法是SHA1,其他的還有md5和3DES。

知道了算法之後,可以自己寫代碼實現加密。
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
看来是没什么更好的方法了
LutzMark 2008-04-02
  • 打赏
  • 举报
回复
默认状态下,membership是采用SHA1的方法进行加密,然后采取一种机制,与passwordsalt进行再次加密,最后形成数据库中显示的密码。密码无法还原就对了。
要改用jsp/php登陆只要知道验证密码的方法就行,1楼说的没错,用Reflector看SqlMemberShip或者找找相关资料
xiaoqiushui 2008-04-02
  • 打赏
  • 举报
回复
boblaw你说的对,我的意思就是这样的,想使用现在的数据库中的用户数据,通过jsp/php来继续访问,密码无法还原。

你提的另外一种方法似乎也有借鉴意义,可是原有的web.config文件中已经指定了enablePasswordRetrieval="false"
那么通过二次加密之后的密码是不是已经不能获得回来了,即便是有PasswordSalt。
boblaw 2008-04-01
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 xiaoqiushui 的回复:]
可是数据库中原来有的数据怎么使用呢?
我面对的问题是要做数据库移植,把原来的用户信息提取出来,
可是密码信息提不出来,于是就想知道他的加密规则,以便模拟认证。
[/Quote]
不好意思,现在才明白你的意思。你的数据库中的用户密码信息已经是使用过ASP.NET的System.Web.Security.MembershipProvider中的加密方法加密过?而现在要改用jsp/php,但是无法还原密码?



对于ASP.NET的MembershipProvider加密算法,我没有研究过,至少使用反编译,未尝试过,也不便多言,但我想应该不是件太容易的事吧。

呵,不过没有那么麻烦,给你个简单的办法:
你目前使用的应该是SqlMembershipProvider类(其实无论哪个提供程序都一样),SqlMembershipProvider类中有受保护的方法DecryptPassword(加密)和EncryptPassword(解密)。
你只要写一个类,继承SqlMembershipProvider类,然后写一个解密方法,在该方法下调用base.EncryptPassword就好了。
然后把数据库中的所有密码数据取出,通过你写的这个方法来一一解密。
至于加密的方法也是这样,LZ举一反三应该没有问题。
怫悰 2008-04-01
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 CloneCenter 的回复:]
那就比较麻烦了!可以用Reflector工具看看SqlMemberShip的实现。
[/Quote]

冒泡帮顶接分

应该是如CloneCenter所说,看看sqlmembership里是怎么加密的了
牛的话用il反汇编
不知道有没有公开的sqlmembership实现代码
xiaoqiushui 2008-04-01
  • 打赏
  • 举报
回复
可是数据库中原来有的数据怎么使用呢?
我面对的问题是要做数据库移植,把原来的用户信息提取出来,
可是密码信息提不出来,于是就想知道他的加密规则,以便模拟认证。
boblaw 2008-04-01
  • 打赏
  • 举报
回复
实现了这个类之后,你要在Web.Config中配置你的提供程序代替ASP.NET默认的MemberShip提供程序。
不过“利用其他脚本比如:jsp,php”的想法可能比较困难。
boblaw 2008-04-01
  • 打赏
  • 举报
回复
使用自定义的提供程序,继承System.Web.Security.MembershipProvider抽象类实现其所有抽象方法即可,内部细节如数据访问由你自己处理。
CloneCenter 2008-04-01
  • 打赏
  • 举报
回复
那就比较麻烦了!可以用Reflector工具看看SqlMemberShip的实现。

17,740

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 .NET Framework
社区管理员
  • .NET Framework社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧