1

一部の古い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 とは異なる方法を使用しましたか? 何が起こっている?

4

5 に答える 5

5

さて、ここに答えがあります:

PHP は、crypt 関数のglibc実装を使用します。(添付: C# 実装)

私の古いパスワードがハッシュと一致しない理由は、私の古い Web サイト (GoDaddy がホスト) が置かれていた Linux ボックスが非標準のハッシュ アルゴリズムを使用していたためです。(おそらく、アルゴリズムで行われた WEIRD の一部を修正するためです。)

ただし、glibc の単体テストと PHP の Windows インストールに対して、次の実装をテストしました。両方のテストに 100% 合格しました。

編集
ここにリンクがあります:(Github Gistに移動しました)

https://gist.github.com/1092558

于 2009-05-14T13:52:01.100 に答える
0

PHPのcrypt()関数は、基礎となるオペレーティング システムがデータを暗号化するために提供するハッシュ アルゴリズムを使用します。そのドキュメントを参照してください。したがって、最初のステップは、データがどのように暗号化されたか (どのハッシュ アルゴリズムが使用されたか) を調べることです。それがわかれば、C# で同じアルゴリズムを見つけるのは簡単です。

于 2009-05-12T22:41:23.177 に答える
0

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);
    ...
}
于 2009-05-14T11:15:27.840 に答える
0

system() (または C# の静的関数が呼び出されたもの) は、暗号化を行う php コマンドライン スクリプトに対していつでも実行できます。

ログインに成功した後でも、パスワードの変更を強制することをお勧めします。次に、ユーザーが変更されたかどうかを示すフラグを設定できます。全員が変更されたら、php 呼び出しをダンプできます。

于 2009-05-12T22:57:59.093 に答える
-1

些細なことではありません。

更新: もともと私は次のように書いていました。それがデジュール・スタンダードかどうかはわかりませんが、デファクト・スタンダードです。そう。

私はそれが些細なことではないように見えるという私の立場を支持します。

コードを移植するのではなく、古い PHP を実行し続け、古いパスワードのパスワード検証に厳密に使用することを検討してください。ユーザーがパスワードを変更するときは、もう少し「オープン」な新しいハッシュ アルゴリズムを使用します。各ユーザーのハッシュと「ハッシュのフレーバー」を保存する必要があります。

于 2009-05-12T22:52:24.050 に答える