4

Недавно я начал разработку нового приложения MVC и потребовался, чтобы взять старую существующую базу данных членства asp.net и преобразовать ее в новую (er) систему идентификации.Миграция из членства в идентификатор при паролеFormat = Шифрование и дешифрование = AES

Если вы оказались в подобной ситуации, то, вероятно, вы столкнулись с этим helpful post from microsoft, который дает вам отличные рекомендации и скрипты по преобразованию базы данных в новую схему, включая пароли.

Для обработки различий в хэшировании/шифровании паролей между двумя системами они включают в себя собственный пользовательский пароль, SqlPasswordHasher, который анализирует поле пароля (которое было объединено в Password | PasswordFormat | Salt) и пытается дублировать логику, найденную внутри SqlMembershipProvider, чтобы сравнить входящий пароль с сохраненной версией.

Однако, поскольку я (и еще один комментатор на этом посту) заметил, что этот удобный хэш-код, который они предоставили, не обрабатывает зашифрованные пароли (несмотря на запутанный лингво, который они используют в сообщении, который, как представляется, указывает на то, что он это делает). Это кажется, как он должен, учитывая, что они принесут через формат паролей в базе данных, но любопытно код не использовать его, а не имея

int passwordformat = 1;

, который для хэшированных паролей. То, что мне было нужно, это тот, который обрабатывал бы мой сценарий, который является зашифрованным паролем, используя decryptionKey для элемента конфигурации System.Web/MachineKey.

Если вы также находитесь в таком затруднительном положении и используете алгоритм AES (как определено в свойстве дешифрования machineKey), то мой ответ ниже должен прийти вам на помощь.

ответ

5

Прежде всего, давайте поговорим очень быстро о том, что делает SqlMembershipProvider под капотом. Поставщик объединяет соль, преобразованную в байт [] с паролем, закодированным как массив байтов юникода, в один больший массив байтов, объединяя их вместе. Довольно просто. Затем он передает это сообщение через абстракцию (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) 
      MemoryStream stream = new MemoryStream(); 
      var aes = new AesCryptoServiceProvider(); 
      aes.Key = HexStringToByteArray(MachineKey.DecryptionKey); 
      aes.GenerateIV(); 
      aes.IV = new byte[aes.IV.Length]; 
      ICryptoTransform transform = aes.CreateEncryptor(); 

      CryptoStream stream2 = new CryptoStream(stream, transform, CryptoStreamMode.Write); 

      stream2.Write(bEncrypt, 0, bEncrypt.Length); 

      stream2.FlushFinalBlock(); 
      bRet = stream.ToArray(); 
      stream2.Close(); 
      //    
     } 

     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 и тщательно прохождения игры, как они инициализация вещи. Счастливое кодирование!