使用Python认证ASP.NET角色/会员创建的数据库中原始用户名、哈希和盐
我们现在有一个应用程序,用户的登录信息存储在SQL Server数据库里。基本上,这些信息包括一个明文的用户名、一个密码的哈希值和一个与这个哈希值相关的盐值。
这些信息都是通过ASP.NET的会员/角色系统自带的功能生成的。比如,有一个名叫“joe”的用户,密码是“password”,他的记录看起来是这样的:
joe,kDP0Py2QwEdJYtUX9cJABg==,OJF6H4KdxFLgLu+oTDNFodCEfMA=
我把这些数据导出了一个CSV文件,现在想把它转换成Django能用的格式。Django存储密码的格式是这样的:
[算法]$[盐值]$[哈希值]
其中,盐值是一个普通字符串,哈希值是SHA1哈希的十六进制摘要。
到目前为止,我发现ASP存储这些哈希值和盐值是用base64格式的。上面的值解码后会变成二进制字符串。
我们使用了反射工具来了解ASP是如何验证这些值的:
internal string EncodePassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0)
{
return pass;
}
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);
}
基本上,它从数据库中提取盐值,并将其进行base64解码,变成二进制表示。然后对原始密码进行“GetBytes”操作,再把盐值放在前面,两个值连接在一起。
接着,它对这个新字符串运行SHA1算法,进行base64编码,并将结果与数据库中存储的值进行比较。
我尝试写一些代码在Python中重现这些哈希值,但一直失败。在我搞清楚如何转换之前,无法在Django中使用这些哈希值。以下是我测试的方式:
import hashlib
from base64 import b64decode, b64encode
b64salt = "kDP0Py2QwEdJYtUX9cJABg=="
b64hash = "OJF6H4KdxFLgLu+oTDNFodCEfMA="
binsalt = b64decode(b64salt)
password_string = 'password'
m1 = hashlib.sha1()
# Pass in salt
m1.update(binsalt)
# Pass in password
m1.update(password_string)
# B64 encode the binary digest
if b64encode(m1.digest()) == b64hash:
print "Logged in!"
else:
print "Didn't match"
print b64hash
print b64encode(m1.digest())
我在想有没有人能发现我方法中的问题,或者能建议其他的解决办法。也许你可以用上面的算法和已知的密码、盐值,在你的系统上生成哈希值?
2 个回答
这里有两个可能出错的地方。
首先,从反射的代码来看,有三种情况:
- 如果 passwordFormat 是 0,密码就会原封不动地返回。
- 如果 passwordFormat 是 1,它会像你的 Python 代码那样生成一个哈希值。
- 如果 passwordFormat 是其他值(不是 0 也不是 1),它会调用 this.EncryptPassword()。
你怎么知道你是在生成哈希值,而不是用 this.EncryptPassword() 来加密密码呢?你可能需要查看 EncryptPassword() 这个函数的具体实现,并试着复制它的逻辑。除非你有一些信息可以确认你是在生成哈希值,而不是在加密。
其次,如果确实是在生成哈希值,你可能想看看 Encoding.Unicode.GetBytes() 函数对字符串 "password" 的返回结果,因为你可能得到的结果是:
0x00 0x70 0x00 0x61 0x00 0x73 0x00 0x73 0x00 0x77 0x00 0x6F 0x00 0x72 0x00 0x64
而不是:
0x70 0x61 0x73 0x73 0x77 0x6F 0x72 0x64
希望这些信息对你有帮助。
看起来在把UTF16字符串转换成二进制的时候,Python会插入一个字节顺序标记(BOM)。而.NET的字节数组里没有这个标记,所以我用了一些简单的Python代码,把UTF16转换成十六进制,去掉前面4个字符,然后再解码成二进制。
可能还有更好的方法来去掉这个BOM,但这个方法对我来说是有效的!
下面是一个可以通过的例子:
import hashlib
from base64 import b64decode, b64encode
def utf16tobin(s):
return s.encode('hex')[4:].decode('hex')
b64salt = "kDP0Py2QwEdJYtUX9cJABg=="
b64hash = "OJF6H4KdxFLgLu+oTDNFodCEfMA="
binsalt = b64decode(b64salt)
password_string = 'password'.encode("utf16")
password_string = utf16tobin(password_string)
m1 = hashlib.sha1()
# Pass in salt
m1.update(binsalt + password_string)
# Pass in password
# B64 encode the binary digest
if b64encode(m1.digest()) == b64hash:
print "Logged in!"
else:
print "Didn't match"
print b64hash
print b64encode(m1.digest())