asp.net - 当passwordFormat = Encrypted且解密= AES时从Membership迁移到Identity

标签 asp.net aes asp.net-identity asp.net-membership asp.net-identity-2

最近,我开始开发一个新的 MVC 应用程序,需要采用旧的现有 asp.net 成员(member)数据库并将其转换为新的身份系统。

如果您发现自己处于类似的情况,那么您可能遇到过此helpful post from microsoft它为您提供了有关将数据库转换为新架构(包括密码)的良好指导和脚本。

为了处理两个系统之间密码散列/加密的差异,它们包含一个自定义密码散列器 SqlPasswordHasher,它解析密码字段(已合并到 Password|PasswordFormat|Salt 中)并尝试复制逻辑在 SqlMembershipProvider 中找到,用于将传入密码与存储的版本进行比较。

然而,正如我(以及该帖子的另一位评论者)注意到的那样,他们提供的这个方便的哈希器不能处理加密密码(尽管他们在帖子中使用了令人困惑的术语,似乎表明它可以处理加密密码)。考虑到他们确实将密码格式带入数据库,看起来似乎应该如此,但奇怪的是代码没有使用它,而是使用了它

int 密码格式 = 1;

用于哈希密码。我需要的是能够处理我的场景的一个方案,即使用 System.Web/MachineKey 配置元素的解密 key 对密码进行加密。

如果您也处于这样的困境,并且正在使用 AES 算法(如 machineKey 的解密属性中所定义),那么我下面的答案应该可以帮助您。

最佳答案

首先,让我们快速讨论一下 SqlMembershipProvider 在幕后所做的事情。提供程序将转换为 byte[] 的盐与密码(编码为 un​​icode 字节数组)组合成一个更大的字节数组,方法是将两者连接在一起。非常简单。然后它通过抽象(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 的源代码并仔细演练它们是如何初始化的。快乐编码!

关于asp.net - 当passwordFormat = Encrypted且解密= AES时从Membership迁移到Identity,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37768496/

相关文章:

azure - 如何使用 Asp.Net Identity 检查 Azure 表中是否存在记录并返回自定义响应

asp.net - 在网站上记录错误的最佳方法

c# - Entity Framework Core 和包括子集合

c# - 如何删除 HttpOnly cookie?

c++ - 如何解密没有明文大小的数据? - openssl加密c++ ubuntu环境

mysql - 如何使用 AES_ENCRYPT 在 MySQL 中安全地存储数据

c# - ClosedXML 安全异常 : Requested registry access is not allowed

java - 使用 AES 和 Cipher 将 java 代码转换为 php

sql-server - ASP.Net Identity 强制从 SQL 注销

entity-framework - 在 MVC 5 项目上使用 ASP.NET 标识,但 httpcontext User.ProviderName 为 "AspNetSqlRoleProvider"