一部の古いALPユーザー アカウントを新しい ASP.Net ソリューションに移植する作業を行っています。ユーザーが古いパスワードを使用できるようにしたいと考えています。
ただし、これを機能させるには、新しく入力したパスワードに基づいて、古いハッシュと新しく計算されたハッシュを比較できる必要があります。
私は周りを検索し、これがPHPによって呼び出される実装として見つかりました:crypt()
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 とは異なる方法を使用しましたか? 何が起こっている?