当 passwordFormat = Encrypted 和 decryption = AES 时从 Membership 迁移到 Identity
Migrating from Membership to Identity when passwordFormat = Encrypted and decryption = AES
最近我开始开发一个新的 MVC 应用程序,需要使用一个旧的 asp.net 会员数据库并将其转换为新的(呃)身份系统。
如果您发现自己处于类似情况,那么您可能已经遇到了这个 helpful post from microsoft,它为您提供了很好的指导和脚本,帮助您将数据库转换为新模式,包括密码。
为了处理两个系统之间 hashing/encryption 密码的差异,它们包含一个自定义密码哈希器 SqlPasswordHasher,它解析密码字段(已合并到 Password|PasswordFormat|Salt)并尝试复制在 SqlMembershipProvider 中找到的逻辑,将传入的密码与存储的版本进行比较。
然而,正如我(和另一位评论者 post)所注意到的,他们提供的这个方便的哈希器不处理加密密码(尽管他们在 post 中使用的令人困惑的行话似乎以表明它确实如此)。它 似乎 应该如此,考虑到他们确实将密码格式带入数据库,但奇怪的是代码没有使用它,而是
int passwordformat = 1;
用于散列密码。我需要的是一个可以处理我使用 System.Web/MachineKey 配置元素的 decryptionKey.
加密密码的方案
如果你也处于这样的困境,并且正在使用AES算法(如machineKey的解密属性中所定义)那么我下面的答案应该可以拯救你。
首先,让我们快速谈谈 SqlMembershipProvider 在幕后做了什么。提供者通过将两者连接在一起,将盐(转换为 byte[] 和密码,编码为 unicode 字节数组)组合成一个更大的字节数组。非常简单。然后它通过抽象 (MembershipAdapter) 将其传递给完成实际工作的 MachineKeySection。
关于该切换的重要部分是它指示 MachineKeySection 使用空 IV(初始化向量)并且不执行签名。那个空的 IV 才是真正的关键,因为 machineKey 元素没有 IV 属性,所以如果您摸不着头脑并且想知道提供者是如何处理这方面的,就是这样。一旦您知道了这一点(通过深入挖掘源代码),您就可以提炼出 MachineKeySection 代码中的加密代码,并将其与会员提供者的代码结合起来,以获得更完整的散列器。完整来源:
public class SQLPasswordHasher : PasswordHasher
{
public override string HashPassword(string password)
{
return base.HashPassword(password);
}
public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
string[] passwordProperties = hashedPassword.Split('|');
if (passwordProperties.Length != 3)
{
return base.VerifyHashedPassword(hashedPassword, providedPassword);
}
else
{
string passwordHash = passwordProperties[0];
int passwordformat = int.Parse(passwordProperties[1]);
string salt = passwordProperties[2];
if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
//This is copied from the existing SQL providers and is provided only for back-compat.
private string EncryptPassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm hm = HashAlgorithm.Create("SHA1");
if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length;)
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
}
else //MembershipPasswordFormat.Encrypted, aka 2
{
byte[] bEncrypt = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bEncrypt, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bEncrypt, bSalt.Length, bIn.Length);
// Distilled from MachineKeyConfigSection EncryptOrDecryptData function, assuming AES algo and paswordCompatMode=Framework20 (the default)
using (var stream = new MemoryStream())
{
var aes = new AesCryptoServiceProvider();
aes.Key = HexStringToByteArray(MachineKey.DecryptionKey);
aes.GenerateIV();
aes.IV = new byte[aes.IV.Length];
using (var transform = aes.CreateEncryptor())
{
using (var stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write))
{
stream2.Write(bEncrypt, 0, bEncrypt.Length);
stream2.FlushFinalBlock();
bRet = stream.ToArray();
}
}
}
}
return Convert.ToBase64String(bRet);
}
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static MachineKeySection MachineKey
{
get
{
//Get encryption and decryption key information from the configuration.
System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
return cfg.GetSection("system.web/machineKey") as MachineKeySection;
}
}
}
如果您有不同的算法,那么步骤将非常接近,但您可能需要先深入了解 MachineKeySection 的源代码并仔细了解它们是如何初始化的。编码愉快!
最近我开始开发一个新的 MVC 应用程序,需要使用一个旧的 asp.net 会员数据库并将其转换为新的(呃)身份系统。
如果您发现自己处于类似情况,那么您可能已经遇到了这个 helpful post from microsoft,它为您提供了很好的指导和脚本,帮助您将数据库转换为新模式,包括密码。
为了处理两个系统之间 hashing/encryption 密码的差异,它们包含一个自定义密码哈希器 SqlPasswordHasher,它解析密码字段(已合并到 Password|PasswordFormat|Salt)并尝试复制在 SqlMembershipProvider 中找到的逻辑,将传入的密码与存储的版本进行比较。
然而,正如我(和另一位评论者 post)所注意到的,他们提供的这个方便的哈希器不处理加密密码(尽管他们在 post 中使用的令人困惑的行话似乎以表明它确实如此)。它 似乎 应该如此,考虑到他们确实将密码格式带入数据库,但奇怪的是代码没有使用它,而是
int passwordformat = 1;
用于散列密码。我需要的是一个可以处理我使用 System.Web/MachineKey 配置元素的 decryptionKey.
加密密码的方案如果你也处于这样的困境,并且正在使用AES算法(如machineKey的解密属性中所定义)那么我下面的答案应该可以拯救你。
首先,让我们快速谈谈 SqlMembershipProvider 在幕后做了什么。提供者通过将两者连接在一起,将盐(转换为 byte[] 和密码,编码为 unicode 字节数组)组合成一个更大的字节数组。非常简单。然后它通过抽象 (MembershipAdapter) 将其传递给完成实际工作的 MachineKeySection。
关于该切换的重要部分是它指示 MachineKeySection 使用空 IV(初始化向量)并且不执行签名。那个空的 IV 才是真正的关键,因为 machineKey 元素没有 IV 属性,所以如果您摸不着头脑并且想知道提供者是如何处理这方面的,就是这样。一旦您知道了这一点(通过深入挖掘源代码),您就可以提炼出 MachineKeySection 代码中的加密代码,并将其与会员提供者的代码结合起来,以获得更完整的散列器。完整来源:
public class SQLPasswordHasher : PasswordHasher
{
public override string HashPassword(string password)
{
return base.HashPassword(password);
}
public override PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
{
string[] passwordProperties = hashedPassword.Split('|');
if (passwordProperties.Length != 3)
{
return base.VerifyHashedPassword(hashedPassword, providedPassword);
}
else
{
string passwordHash = passwordProperties[0];
int passwordformat = int.Parse(passwordProperties[1]);
string salt = passwordProperties[2];
if (String.Equals(EncryptPassword(providedPassword, passwordformat, salt), passwordHash, StringComparison.CurrentCultureIgnoreCase))
{
return PasswordVerificationResult.SuccessRehashNeeded;
}
else
{
return PasswordVerificationResult.Failed;
}
}
}
//This is copied from the existing SQL providers and is provided only for back-compat.
private string EncryptPassword(string pass, int passwordFormat, string salt)
{
if (passwordFormat == 0) // MembershipPasswordFormat.Clear
return pass;
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(salt);
byte[] bRet = null;
if (passwordFormat == 1)
{ // MembershipPasswordFormat.Hashed
HashAlgorithm hm = HashAlgorithm.Create("SHA1");
if (hm is KeyedHashAlgorithm)
{
KeyedHashAlgorithm kha = (KeyedHashAlgorithm)hm;
if (kha.Key.Length == bSalt.Length)
{
kha.Key = bSalt;
}
else if (kha.Key.Length < bSalt.Length)
{
byte[] bKey = new byte[kha.Key.Length];
Buffer.BlockCopy(bSalt, 0, bKey, 0, bKey.Length);
kha.Key = bKey;
}
else
{
byte[] bKey = new byte[kha.Key.Length];
for (int iter = 0; iter < bKey.Length;)
{
int len = Math.Min(bSalt.Length, bKey.Length - iter);
Buffer.BlockCopy(bSalt, 0, bKey, iter, len);
iter += len;
}
kha.Key = bKey;
}
bRet = kha.ComputeHash(bIn);
}
else
{
byte[] bAll = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = hm.ComputeHash(bAll);
}
}
else //MembershipPasswordFormat.Encrypted, aka 2
{
byte[] bEncrypt = new byte[bSalt.Length + bIn.Length];
Buffer.BlockCopy(bSalt, 0, bEncrypt, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bEncrypt, bSalt.Length, bIn.Length);
// Distilled from MachineKeyConfigSection EncryptOrDecryptData function, assuming AES algo and paswordCompatMode=Framework20 (the default)
using (var stream = new MemoryStream())
{
var aes = new AesCryptoServiceProvider();
aes.Key = HexStringToByteArray(MachineKey.DecryptionKey);
aes.GenerateIV();
aes.IV = new byte[aes.IV.Length];
using (var transform = aes.CreateEncryptor())
{
using (var stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write))
{
stream2.Write(bEncrypt, 0, bEncrypt.Length);
stream2.FlushFinalBlock();
bRet = stream.ToArray();
}
}
}
}
return Convert.ToBase64String(bRet);
}
public static byte[] HexStringToByteArray(String hex)
{
int NumberChars = hex.Length;
byte[] bytes = new byte[NumberChars / 2];
for (int i = 0; i < NumberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static MachineKeySection MachineKey
{
get
{
//Get encryption and decryption key information from the configuration.
System.Configuration.Configuration cfg = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
return cfg.GetSection("system.web/machineKey") as MachineKeySection;
}
}
}
如果您有不同的算法,那么步骤将非常接近,但您可能需要先深入了解 MachineKeySection 的源代码并仔细了解它们是如何初始化的。编码愉快!