2009-05-12 4 views
0

으로 이식 할 때 문제가 발생했습니다. 이전의 ALP 사용자 계정을 새로운 ASP.Net 솔루션으로 이식하는 중 일하고 있습니다. 사용자가 이전 암호를 사용할 수 있기를 바랍니다.PHP crypt() 함수를 C#

그러나 작동하려면 이전 유형의 해시를 새로 입력 한 비밀번호를 기반으로 새로 계산 된 암호와 비교할 수 있어야합니다. 여기에 C#에서 내 버전이 예상되는 경기와 함께,이다,

char * 
crypt_md5(const char *pw, const char *salt) 
{ 
    MD5_CTX ctx,ctx1; 
    unsigned long l; 
    int sl, pl; 
    u_int i; 
    u_char final[MD5_SIZE]; 
    static const char *sp, *ep; 
    static char passwd[120], *p; 
    static const char *magic = "$1$"; 

    /* Refine the Salt first */ 
    sp = salt; 

    /* If it starts with the magic string, then skip that */ 
    if(!strncmp(sp, magic, strlen(magic))) 
     sp += strlen(magic); 

    /* It stops at the first '$', max 8 chars */ 
    for(ep = sp; *ep && *ep != '$' && ep < (sp + 8); ep++) 
     continue; 

    /* get the length of the true salt */ 
    sl = ep - sp; 

    MD5Init(&ctx); 

    /* The password first, since that is what is most unknown */ 
    MD5Update(&ctx, (const u_char *)pw, strlen(pw)); 

    /* Then our magic string */ 
    MD5Update(&ctx, (const u_char *)magic, strlen(magic)); 

    /* Then the raw salt */ 
    MD5Update(&ctx, (const u_char *)sp, (u_int)sl); 

    /* Then just as many characters of the MD5(pw,salt,pw) */ 
    MD5Init(&ctx1); 
    MD5Update(&ctx1, (const u_char *)pw, strlen(pw)); 
    MD5Update(&ctx1, (const u_char *)sp, (u_int)sl); 
    MD5Update(&ctx1, (const u_char *)pw, strlen(pw)); 
    MD5Final(final, &ctx1); 
    for(pl = (int)strlen(pw); pl > 0; pl -= MD5_SIZE) 
     MD5Update(&ctx, (const u_char *)final, 
      (u_int)(pl > MD5_SIZE ? MD5_SIZE : pl)); 

    /* Don't leave anything around in vm they could use. */ 
    memset(final, 0, sizeof(final)); 

    /* Then something really weird... */ 
    for (i = strlen(pw); i; i >>= 1) 
     if(i & 1) 
      MD5Update(&ctx, (const u_char *)final, 1); 
     else 
      MD5Update(&ctx, (const u_char *)pw, 1); 

    /* Now make the output string */ 
    strcpy(passwd, magic); 
    strncat(passwd, sp, (u_int)sl); 
    strcat(passwd, "$"); 

    MD5Final(final, &ctx); 

    /* 
    * and now, just to make sure things don't run too fast 
    * On a 60 Mhz Pentium this takes 34 msec, so you would 
    * need 30 seconds to build a 1000 entry dictionary... 
    */ 
    for(i = 0; i < 1000; i++) { 
     MD5Init(&ctx1); 
     if(i & 1) 
      MD5Update(&ctx1, (const u_char *)pw, strlen(pw)); 
     else 
      MD5Update(&ctx1, (const u_char *)final, MD5_SIZE); 

     if(i % 3) 
      MD5Update(&ctx1, (const u_char *)sp, (u_int)sl); 

     if(i % 7) 
      MD5Update(&ctx1, (const u_char *)pw, strlen(pw)); 

     if(i & 1) 
      MD5Update(&ctx1, (const u_char *)final, MD5_SIZE); 
     else 
      MD5Update(&ctx1, (const u_char *)pw, strlen(pw)); 
     MD5Final(final, &ctx1); 
    } 

    p = passwd + strlen(passwd); 

    l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; 
    _crypt_to64(p, l, 4); p += 4; 
    l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; 
    _crypt_to64(p, l, 4); p += 4; 
    l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; 
    _crypt_to64(p, l, 4); p += 4; 
    l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; 
    _crypt_to64(p, l, 4); p += 4; 
    l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; 
    _crypt_to64(p, l, 4); p += 4; 
    l = final[11]; 
    _crypt_to64(p, l, 2); p += 2; 
    *p = '\0'; 

    /* Don't leave anything around in vm they could use. */ 
    memset(final, 0, sizeof(final)); 

    return (passwd); 
} 

그리고 :

나는 주변 검색, 그리고 PHP에 의해 호출 crypt()의 구현으로 this을 발견했다.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 
using System.Security.Cryptography; 
using System.IO; 
using System.Management; 

namespace Test 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      byte[] salt = Encoding.ASCII.GetBytes("$1$ls3xPLpO$Wu/FQ.PtP2XBCqrM.w847/"); 
      Console.WriteLine("Hash: " + Encoding.ASCII.GetString(salt)); 

      byte[] passkey = Encoding.ASCII.GetBytes("suckit"); 

      byte[] newhash = md5_crypt(passkey, salt); 
      Console.WriteLine("Hash2: " + Encoding.ASCII.GetString(newhash)); 

      byte[] newhash2 = md5_crypt(passkey, newhash); 
      Console.WriteLine("Hash3: " + Encoding.ASCII.GetString(newhash2)); 


      Console.ReadKey(true); 
     } 

     public static byte[] md5_crypt(byte[] pw, byte[] salt) 
     { 
      MemoryStream ctx, ctx1; 
      ulong l; 
      int sl, pl; 
      int i; 
      byte[] final; 
      int sp, ep; //** changed pointers to array indices 
      MemoryStream passwd = new MemoryStream(); 
      byte[] magic = Encoding.ASCII.GetBytes("$1$"); 

      // Refine the salt first 
      sp = 0; //** Changed to an array index, rather than a pointer. 

      // If it starts with the magic string, then skip that 
      if (salt[0] == magic[0] && 
       salt[1] == magic[1] && 
       salt[2] == magic[2]) 
      { 
       sp += magic.Length; 
      } 

      // It stops at the first '$', max 8 chars 
      for (ep = sp; 
       (ep + sp < salt.Length) && //** Converted to array indices, and rather than check for null termination, check for the end of the array. 
       salt[ep] != (byte)'$' && 
       ep < (sp + 8); 
       ep++) 
       continue; 

      // Get the length of the true salt 
      sl = ep - sp; 

      ctx = MD5Init(); 

      // The password first, since that is what is most unknown 
      MD5Update(ctx, pw, pw.Length); 

      // Then our magic string 
      MD5Update(ctx, magic, magic.Length); 

      // Then the raw salt 
      MD5Update(ctx, salt, sp, sl); 

      // Then just as many characters of the MD5(pw,salt,pw) 
      ctx1 = MD5Init(); 
      MD5Update(ctx1, pw, pw.Length); 
      MD5Update(ctx1, salt, sp, sl); 
      MD5Update(ctx1, pw, pw.Length); 
      final = MD5Final(ctx1); 
      for(pl = pw.Length; pl > 0; pl -= final.Length) 
       MD5Update(ctx, final, 
        (pl > final.Length ? final.Length : pl)); 

      // Don't leave anything around in vm they could use. 
      for (i = 0; i < final.Length; i++) final[i] = 0; 

      // Then something really weird... 
      for (i = pw.Length; i != 0; i >>= 1) 
       if((i & 1) != 0) 
        MD5Update(ctx, final, 1); 
       else 
        MD5Update(ctx, pw, 1); 


      // Now make the output string 
      passwd.Write(magic, 0, magic.Length); 
      passwd.Write(salt, sp, sl); 
      passwd.WriteByte((byte)'$'); 

      final = MD5Final(ctx); 

      // and now, just to make sure things don't run too fast 
      // On a 60 Mhz Pentium this takes 34 msec, so you would 
      // need 30 seconds to build a 1000 entry dictionary... 
      for(i = 0; i < 1000; i++) 
      { 
       ctx1 = MD5Init(); 
       if((i & 1) != 0) 
        MD5Update(ctx1, pw, pw.Length); 
       else 
        MD5Update(ctx1, final, final.Length); 

       if((i % 3) != 0) 
        MD5Update(ctx1, salt, sp, sl); 

       if((i % 7) != 0) 
        MD5Update(ctx1, pw, pw.Length); 

       if((i & 1) != 0) 
        MD5Update(ctx1, final, final.Length); 
       else 
        MD5Update(ctx1, pw, pw.Length); 

       final = MD5Final(ctx1); 
      } 

      //** Section changed to use a memory stream, rather than a byte array. 
      l = (((ulong)final[0]) << 16) | (((ulong)final[6]) << 8) | ((ulong)final[12]); 
      _crypt_to64(passwd, l, 4); 
      l = (((ulong)final[1]) << 16) | (((ulong)final[7]) << 8) | ((ulong)final[13]); 
      _crypt_to64(passwd, l, 4); 
      l = (((ulong)final[2]) << 16) | (((ulong)final[8]) << 8) | ((ulong)final[14]); 
      _crypt_to64(passwd, l, 4); 
      l = (((ulong)final[3]) << 16) | (((ulong)final[9]) << 8) | ((ulong)final[15]); 
      _crypt_to64(passwd, l, 4); 
      l = (((ulong)final[4]) << 16) | (((ulong)final[10]) << 8) | ((ulong)final[5]); 
      _crypt_to64(passwd, l, 4); 
      l = final[11]; 
      _crypt_to64(passwd, l, 2); 

      byte[] buffer = new byte[passwd.Length]; 
      passwd.Seek(0, SeekOrigin.Begin); 
      passwd.Read(buffer, 0, buffer.Length); 
      return buffer; 
     } 

     public static MemoryStream MD5Init() 
     { 
      return new MemoryStream(); 
     } 

     public static void MD5Update(MemoryStream context, byte[] source, int length) 
     { 
      context.Write(source, 0, length); 
     } 

     public static void MD5Update(MemoryStream context, byte[] source, int offset, int length) 
     { 
      context.Write(source, offset, length); 
     } 

     public static byte[] MD5Final(MemoryStream context) 
     { 
      long location = context.Position; 
      byte[] buffer = new byte[context.Length]; 
      context.Seek(0, SeekOrigin.Begin); 
      context.Read(buffer, 0, (int)context.Length); 
      context.Seek(location, SeekOrigin.Begin); 
      return MD5.Create().ComputeHash(buffer); 
     } 

     // Changed to use a memory stream rather than a character array. 
     public static void _crypt_to64(MemoryStream s, ulong v, int n) 
     { 
      char[] _crypt_a64 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray(); 

      while (--n >= 0) 
      { 
       s.WriteByte((byte)_crypt_a64[v & 0x3f]); 
       v >>= 6; 
      } 
     } 


    } 
} 

무엇이 잘못 되었나요? FreeBSD 버전에서 MD5xxxx 함수의 작동에 대해 몇 가지 큰 가정을하고 있지만 작동하는 것 같습니다.

PHP에서 사용하는 실제 버전이 아닙니까? 누구든지 어떤 통찰력을 가지고 있습니까?

편집 :

나는 PHP의 소스 코드의 복사본을 다운로드하고이의 glibc 라이브러리를 사용하는 것을 발견했다. 그래서, glibc의 소스 코드 사본을 다운로드하고, __md5_crypt_r 함수를 찾고, 그 기능을 복제했습니다. FreeBSD 버전과 똑같은 해시로 돌아 왔습니다.

지금, 나는 꽤 어려움을 겪고 있습니다. PHP 4가 PHP 5와 다른 방법을 사용 했습니까? 무슨 일 이니?

+0

당신이 걸릴나요 (A Github의 요점로 이동) PHP의 실제 소스에서 C 코드가 : 여기에 편집

링크입니까? crypt() 구현을 살펴 보았습니까? –

답변

4

좋아, 그래서 여기에 대한 답은 다음과 같습니다

PHP는 토굴 함수의 의 glibc 구현을 사용합니다. (첨부 : C# 구현)

내 이전 암호가 해시와 일치하지 않는 이유는 기존의 웹 사이트 (GoDaddy에서 호스트 됨)의 Linux 상자에 비표준 해싱 알고리즘이 있었기 때문입니다. (아마도 알고리즘에서 수행 된 WEIRD의 일부를 고칠 수 있습니다.)

그러나 glibc의 단위 테스트와 PHP의 Windows 설치에 대해 다음 구현을 테스트했습니다. 두 검사 모두 100 % 통과했습니다.

https://gist.github.com/1092558

+0

"C의 char * type"을 에뮬레이트하는 데 사용되는 이유는 C#에서 char *를 사용하는 것이 어떨까요? –

+0

그러면 UNSAFE 코드 블록에 래핑해야합니다. 여기에 매우 바람직하지 않습니다. –

+0

전체 코드에 대한 링크를 게시 할 수 있습니까? –

0

PHP의 crypt() 함수는 기본 운영 체제에서 데이터를 암호화하기 위해 제공하는 해시 알고리즘을 사용합니다. 해당 설명서를 살펴보십시오. 따라서 첫 번째 단계는 데이터가 암호화 된 방법 (해시 알고리즘이 사용 된 방법)을 알아내는 것입니다. 일단 알고 있으면 C#과 동일한 알고리즘을 찾는 것이 쉽습니다.

+0

실제로는 올바르지 않습니다. DES, MD5 또는 Blowfish 알고리즘을 사용하지만 실제 알고리즘 자체는 직접적인 해시가 아닙니다. MD5 버전의 경우에는 운영 체제의 구현을 사용하지 않고 젠드 만의 구현을 사용합니다. 사양을 읽으면 MD5 부분 (내가 필요로하는 부분)도 완전히 명확하지 않은 염화 메커니즘을 사용한다는 것을 알 수 있습니다. –

+0

MD5의 젠드 구현은 PHP 5.3에서 도입되었습니다. 매뉴얼을 읽어보십시오. – soulmerge

-1

사소한 것처럼 보입니다.

UPDATE이 : 원래 내가 쓴 : ".?. PHP의 토굴 함수는 표준 해시처럼 보이지 않는 왜 누가 알고을"의견에서 지적한 것처럼, PHP의 토굴은()과 동일 passwd crypt에 BSD에서 사용되었습니다. 나는 그것이 표준인지 아닌지는 모르겠지만 그것은 표준이다. 그래서.

나는 내 입장에 서서 사소한 것처럼 보이지 않는다.

코드를 이식하는 것이 아니라 이전 PHP를 계속 실행하고 이전 암호의 암호 유효성 검사에 엄격하게 사용하는 것이 좋습니다. 사용자가 암호를 변경하면 새로운 해시 알고리즘을 사용하여 좀 더 "열린"상태를 유지하십시오. 각 사용자에 대해 해시 및 "해시의 맛"을 저장해야합니다.

+0

왜 이런 일이 일어 났습니까? 내 것은 속는 사람이지만 바퀴를 다시 발명하지 않으면 아무런 잘못이 없습니다. – jmucchiello

+0

나는 당신의 답을 땡땡이 떨지 않았다. 그러나 나는 그것이 적어도 질문에 대답하지 않기 때문에 아마도 그것이 들려 왔다는 것을 당신에게 알려주고 싶다. 기능을 이식하는 실제 작업에 도움이되지 않는 것 외에도 기존 및 새 응용 프로그램 간의 실제 통신 수단없이 실행중인 추가 환경을 유지할 것을 제안합니다. –

+0

PHP 환경이 중단되었다는 점을 놓쳤습니다. 나는 이전의 해시와 새로운 해시를 비교해야한다는 명확하게 명시된 요구 사항을 해결하기 위해 노력하고있었습니다. 기존 PHP를 사용하면이 문제를 해결할 수 있으므로 필자의 답변은 사용자의 요구 사항에 대한 직접적인 대답이라고 생각됩니다. PORT 코드에 대한 귀하의 요구 사항을 이해하지 못했습니다. – Cheeso

0

언제든지 system() (또는 C# 정적 함수가 호출되는 모든 것)을 암호화 할 수있는 PHP 명령 줄 스크립트로 내보낼 수 있습니다.

로그인을 완료 한 후에도 비밀번호를 변경하는 것이 좋습니다. 그런 다음 사용자가 변경되었는지 여부를 나타내는 플래그를 가질 수 있습니다. 일단 모두가 변경되면 PHP 호출을 덤프 할 수 있습니다.

+0

저는이 솔루션을 다른 해킹 솔루션보다 더 좋아합니다. PHP는 로컬 스크립팅 호스트로 설치할 수 있기 때문입니다. 그러나, 제 경우에는 사소한 기능만을 제공하기 위해 제 제작 환경에 PHP를 설치하고 싶지 않습니다. –

0

PHP 구현을 다시 사용하십시오 ... PHP의 암호화 라이브러리가 시스템 환경 경로에 있는지 확인하십시오.

문자열 마샬링/charset이 올바른지 확인하기 위해 interop 메서드를 업데이트해야 할 수도 있습니다. 원래 해시 알고리즘을 사용할 수 있습니다.

[DllImport("crypt.dll", CharSet=CharSet.ASCII)] 
private static extern string crypt(string password, string salt); 

public bool ValidLogin(string username, string password) 
{ 
    string hash = crypt(password, null); 
    ... 
} 
+0

PHP가 스크립팅 언어이기 때문에 매개 변수가 이상한 방식으로 함수에 전달됩니다. 좋은 생각이지만, 나는 이미 그렇게 생각하지 않았다고 말할 수는 없습니다. –

관련 문제