4

최근에 새로운 MVC 응용 프로그램을 개발하기 시작했으며 이전의 기존 asp.net 회원 데이터베이스를 가져와 새로운 ID 시스템으로 변환해야했습니다.passwordFormat = Encrypted and decryption = AES

이와 비슷한 상황에 처한 사람은 helpful post from microsoft이되어 암호를 포함하여 새로운 스키마로 데이터베이스를 변환하는 데 유용한 지침과 스크립트를 제공합니다.

두 시스템 간의 암호 해시/암호화의 차이점을 처리하기 위해 암호 필드 (암호 | PasswordFormat | Salt에 결합 됨)를 구문 분석하고 암호를 복제하려고 시도하는 사용자 지정 암호 해더 인 SqlPasswordHasher를 포함합니다. 들어오는 암호와 저장된 버전을 비교하기 위해 SqlMembershipProvider 내부에있는 논리.

그러나 나는 (그 게시물에 대한 또 다른 주석 작성자가)주의했듯이 그들이 제공 한이 핸디 해더는 암호화 된 비밀번호를 처리하지 않습니다. (게시물에서 사용하는 혼란스러운 말투에도 불구하고 나타납니다. 그것은 은 데이터베이스에 암호 형식을 통해 가져 않습니다 고려,이해야처럼 보인다,하지만 호기심 코드 대신 해시 된 암호입니다

int passwordformat = 1;

을 가지고, 그것을 사용하지 않습니다. 필자가 필요로했던 시나리오는 System.Web/MachineKey 구성 요소의 decryptionKey를 사용하여 암호화 된 암호 인 시나리오를 처리하는 것이 었습니다.

또한 이러한 곤경에 처한 경우 (machineKey의 암호 해독 속성에 정의 된대로) AES 알고리즘을 사용하는 경우 아래 답변을 구해야합니다.

답변

5

먼저 SqlMembershipProvider가 수행중인 작업에 대해 간단하게 설명하겠습니다. 공급자는 바이트 []와 암호를 유니 코드 바이트 배열로 인코딩 된 암호를 결합하여 두 개의 바이트를 서로 연결하여 더 큰 단일 바이트 배열로 결합합니다. 꽤 직설적 인. 그런 다음 추상화 (MembershipAdapter)를 통해 실제 작업이 완료된 MachineKeySection으로 전달합니다.

이 핸드 오프의 중요한 부분은 빈 IV (초기화 벡터)를 사용하고 서명을 수행하지 않도록 MachineKeySection에 지시한다는 것입니다. 그 빈 IV는 진짜 lynchpin입니다. 왜냐하면 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의 소스에 첫 번째 다이빙을 원하고 그들이 일을 초기화하는 방법을주의 깊게 둘러 보지 수 있습니다. 해피 코딩!