سؤال

أنا أعمل على نقل بعض القديم Alp حسابات المستخدم إلى حل جديد 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;
            }
        }


    }
}

ما الخطأ الذي افعله؟ أقوم ببعض الافتراضات الكبيرة حول عمل وظائف MD5XXXX في إصدار FreeBSD ، ولكن يبدو أنه يعمل.

أليس هذا هو الإصدار الفعلي الذي يستخدمه PHP؟ هل لدى أي شخص أي رؤية؟

تعديل:

قمت بتنزيل نسخة من رمز مصدر PHP ، ووجدت أنه يستخدم مكتبة GLIBC. لذلك ، قمت بتنزيل نسخة من رمز مصدر GLIBC ، وجدت وظيفة __md5_crypt_r ، وتكرار وظائفها ، وعادت مع نفس تجزئة مثل إصدار FreebOBSD.

الآن ، أنا متعثر إلى حد كبير. هل استخدم PHP 4 طريقة مختلفة عن PHP 5؟ ما الذي يجري؟

هل كانت مفيدة؟

المحلول

حسنا ، هنا هو الجواب:

يستخدم PHP glibc تنفيذ وظيفة سرداب. (مرفق: C# التنفيذ)

السبب في أن كلمات المرور القديمة الخاصة بي لا تتطابق مع التجزئة هو أن مربع Linux الخاص بي القديم الخاص بي (الذي استضافته Godaddy) جلس على خوارزمية تجزئة غير قياسية. (ربما لإصلاح بعض الأشياء الغريبة التي تم إجراؤها في الخوارزمية.)

ومع ذلك ، فقد اختبرت التنفيذ التالي مقابل اختبارات وحدة GLIBC وضد تثبيت Windows لـ PHP. تم اجتياز كلا الاختبارين 100 ٪.

تعديل
هنا الرابط: (تم نقله إلى Github Gist)

https://gist.github.com/1092558

نصائح أخرى

ال سرداب() تستخدم الوظيفة في PHP أي خوارزمية التجزئة التي يوفرها نظام التشغيل الأساسي لتشفير البيانات - إلقاء نظرة على وثائقها. لذلك يجب أن تكون الخطوة الأولى هي معرفة ذلك ، كيف تم تشفير البيانات (ما تم استخدام خوارزمية التجزئة). بمجرد أن تعرف ذلك ، يجب أن يكون من التافهة العثور على نفس الخوارزمية لـ C#.

يمكنك دائمًا استدعاء System () (أو أيا كان الوظيفة الثابتة C#) إلى نص سطر الأوامر PHP الذي يقوم بعمل سرداب لك.

أوصي بفرض تغيير كلمة المرور على الرغم من ذلك بعد تسجيل الدخول الناجح. ثم يمكنك الحصول على علامة تشير إلى ما إذا كان المستخدم قد تغير. بمجرد أن يتغير الجميع ، يمكنك التخلص من مكالمة PHP.

ما عليك سوى إعادة استخدام تطبيق PHP ... تأكد من أن مكتبات Crypt الخاصة بـ PHP في مسار بيئة النظام الخاص بك ...

قد تحتاج إلى تحديث طريقة interop الخاصة بك للتأكد من صحة حشط السلسلة/charset ... يمكنك بعد ذلك استخدام خوارزمية التجزئة الأصلية.

[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. لا أعرف ما إذا كان هذا هو معيار إزالة ، لكنه معيار Defacto. So.

أقف إلى جانب موقفي أنه لا يبدو أنه تافه.

بدلاً من نقل الكود ، قد تفكر في الحفاظ على تشغيل PHP القديم ، واستخدامه بشكل صارم للتحقق من صحة كلمة المرور من كلمات المرور القديمة. نظرًا لأن المستخدمين يغيرون كلمات المرور الخاصة بهم ، استخدم خوارزمية تجزئة جديدة ، شيء أكثر "مفتوحًا". سيكون عليك تخزين التجزئة ، وكذلك "نكهة التجزئة" لكل مستخدم.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top