문제

나는 오래된 포팅을 위해 노력하고 있습니다 높은 산 사용자는 새로운 ASP.NET 솔루션을 계정하며 사용자가 이전 비밀번호를 사용할 수 있기를 바랍니다.

그러나 그것이 작동하려면 새로 타이핑 된 비밀번호를 기반으로 이전 해시를 새로 계산 된 해시와 비교할 수 있어야합니다.

나는 찾아서 발견했다 이것 구현으로 crypt() PHP에 의해 호출 :

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);
}

그리고 여기 C#의 내 버전과 예상 일치가 있습니다.

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 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".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와 다른 방법을 사용 했습니까? 무슨 일이 일어나고 있습니까?

도움이 되었습니까?

해결책

좋아요, 여기에 답이 있습니다.

PHP를 사용합니다 glibc Crypt 기능의 구현. (첨부 : C# 구현)

내 기존 암호가 해시와 일치하지 않는 이유는 내 이전 웹 사이트 (Godaddy가 호스팅 한)에 비표준 해싱 알고리즘이 있었기 때문입니다. (아마도 알고리즘에서 수행 된 이상한 것들을 고치기 위해.)

그러나 GLIBC의 단위 테스트와 PHP의 Windows 설치에 대해 다음과 같은 구현을 테스트했습니다. 두 테스트 모두 100%전달되었습니다.

편집하다
링크는 다음과 같습니다. (Github Gist로 이동)

https://gist.github.com/1092558

다른 팁

그만큼 토굴() PHP의 기능은 해시 알고리즘을 사용합니다. 기본 운영 체제가 데이터를 암호화하기 위해 제공하는 모든 해시 알고리즘을 사용합니다. 문서를 살펴 봅니다. 따라서 첫 번째 단계는 데이터가 어떻게 암호화되었는지 (해싱 알고리즘을 사용한 것)를 알아내는 것입니다. 일단 알고 나면 C#에 대한 동일한 알고리즘을 찾는 것은 사소한 일이어야합니다.

당신은 항상 system (또는 c# static 함수가 호출되는 모든 것에 따라)을 Crypt를 수행하는 PHP 명령 줄 스크립트로 만들 수 있습니다.

성공적인 로그인 후에도 암호 변경을 강요하는 것이 좋습니다. 그런 다음 사용자가 변경되었는지 표시하는 플래그를 가질 수 있습니다. 모든 사람이 변경되면 PHP 호출을 덤프 할 수 있습니다.

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

String Marshaling/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);
    ...
}

사소한 것처럼 보이지 않습니다.

업데이트: 원래 나는 다음과 같이 썼다. "PHP Crypt 기능은 표준 해시처럼 보이지 않습니다. 왜 안 돼? 누가 알아."주석에서 지적한 바와 같이, PHP crypt ()는 Passwd Crypt에 BSD에서 사용 된 것과 동일합니다. 이것이 하락 표준인지는 모르겠지만 Defacto 표준입니다.

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

코드를 포팅하는 대신 이전 PHP를 실행하는 것을 고려하고 이전 암호의 비밀번호 검증에 엄격하게 사용할 수 있습니다. 사용자가 비밀번호를 변경하면 새로운 해싱 알고리즘을 사용하여 조금 더 "열린"것입니다. 각 사용자의 "해시 맛"뿐만 아니라 해시를 저장해야합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top