【问题标题】:Migrating from Membership to Identity when passwordFormat = Encrypted and decryption = AES当密码格式 = 加密和解密 = AES 时从成员身份迁移到身份
【发布时间】:2016-06-11 21:03:43
【问题描述】:

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

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

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

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

int passwordformat = 1;

用于散列密码。我需要的是一个可以处理我的场景的场景,即使用 System.Web/MachineKey 配置元素的 decryptionKey 加密密码。

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

【问题讨论】:

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


    【解决方案1】:

    首先,让我们快速讨论一下 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 的源代码,并仔细了解它们是如何初始化事物的。快乐编码!

    【讨论】:

      猜你喜欢
      • 2019-05-08
      • 1970-01-01
      • 1970-01-01
      • 2014-07-31
      • 1970-01-01
      • 2014-05-30
      • 2012-12-17
      • 1970-01-01
      • 2011-05-08
      相关资源
      最近更新 更多