Вопрос

Я работаю над портированием некоторых старых ALP ( АЛЬП ) учетные записи пользователей на новый ASP.Сетевое решение, и я хотел бы, чтобы пользователи могли использовать свои старые пароли.

Однако для того, чтобы это сработало, мне нужно иметь возможность сравнивать старые хэши с недавно вычисленными на основе недавно введенного пароля.

Я поискал вокруг и нашел это поскольку реализация 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;
            }
        }


    }
}

Что я делаю не так?Я делаю некоторые серьезные предположения о работе функций MD5xxxx в версии FreeBSD, но, похоже, это работает.

Разве это не фактическая версия, используемая PHP?У кого-нибудь есть какое-нибудь представление?

Редактировать:

Я скачал копию исходного кода PHP и обнаружил, что он использует библиотеку glibc.Итак, я скачал копию исходного кода glibc, нашел функцию __md5_crypt_r, продублировал ее функциональность, и она вернулась с ТОЧНО такими же хэшами, что и в версии FreeBSD.

Теперь я в значительной степени поставлен в тупик.Использовал ли PHP 4 другой метод, чем PHP 5?Что происходит?

Это было полезно?

Решение

Хорошо, итак, вот ответ:

PHP использует glibc ( глибц ) реализация функции crypt.(прилагается:Реализация на C#)

Причина, по которой мои старые пароли не соответствуют хэшу, заключается в том, что окно Linux, на котором находился мой старый веб-сайт (размещенный GoDaddy), имело нестандартный алгоритм хэширования.(Возможно, чтобы исправить некоторые СТРАННЫЕ вещи, сделанные в алгоритме.)

Однако я протестировал следующую реализацию в модульных тестах glibc и при установке PHP для Windows.Оба теста были пройдены на 100%.

Редактировать
Вот ссылка:(перенесен на Github Gist)

https://gist.github.com/1092558

Другие советы

В склеп() функция в PHP использует любой алгоритм хэширования, который базовая операционная система предоставляет для шифрования данных - взгляните на ее документацию.Итак, первым шагом должно быть выяснение того, как данные были зашифрованы (какой алгоритм хеширования использовался).Как только вы это узнаете, найти тот же алгоритм для C # будет тривиально.

Вы всегда можете вызвать system() (или любую другую статическую функцию C #) в скрипте командной строки php, который выполняет криптографию за вас.

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

Просто повторно используйте реализацию php...Убедитесь, что библиотеки php crypt находятся в вашем системном пути к среде...

Возможно, вам потребуется обновить ваш метод взаимодействия, чтобы убедиться, что ваш маршалинг строк / кодировка верны...затем вы можете использовать оригинальный алгоритм хеширования.

[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() - это то же самое, что используется в BSD для passwd crypt.Я не знаю, является ли это стандартом dejure, но это стандарт defacto.Итак.

Я придерживаюсь своей позиции, что это не кажется тривиальным.

Вместо того чтобы переносить код, вы могли бы рассмотреть возможность сохранения старого PHP запущенным и использовать его строго для проверки старых паролей.Когда пользователи меняют свои пароли, используйте новый алгоритм хеширования, что-то немного более "открытое".Вам пришлось бы хранить хэш, а также "вкус хэша" для каждого пользователя.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top