将PHP crypt()函数移植到C#的问题

我正在努力将一些旧的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  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  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 >= 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]) <= 0) { s.WriteByte((byte)_crypt_a64[v & 0x3f]); v >>= 6; } } } } 

我究竟做错了什么? 我正在对FreeBSD版本中的MD5xxxx函数的工作做一些大的假设,但它似乎有效。

这不是PHP使用的实际版本吗? 有没有人有任何见解?

编辑:

我下载了PHP的源代码副本,发现它使用了glibc库。 所以,我下载了一份glibc的源代码,发现了__md5_crypt_r函数,复制了它的function,它带有与FreeBSD版本完全相同的哈希值。

现在,我非常难过。 PHP 4使用的方法与PHP 5不同吗? 到底是怎么回事?

好的,所以这里是答案:

PHP使用crypt函数的glibc实现。 (附:C#实现)

我的旧密码与哈希不匹配的原因是因为我的旧网站(由GoDaddy托管)所在的Linux机箱有一个非标准的哈希算法。 (可能要修复算法中完成的一些WEIRD内容。)

但是,我已经针对glibc的unit testing和PHP的Windows安装测试了以下实现。 两项测试均通过100%。

编辑
这是链接:(移动到Github Gist)

https://gist.github.com/1092558

PHP中的crypt()函数使用底层操作系统提供的加密数据的任何哈希算法 – 查看其文档。 因此,第一步应该是找出数据是如何加密的(使用了什么散列算法)。 一旦你知道了,为C#找到相同的算法应该是微不足道的。

你可以随时将system()(或任何调用C#静态函数)输出到为你执行crypt的php命令行脚本。

成功登录后,我建议强制更改密码。 然后,您可以拥有一个标志,指示用户是否已更改。 一旦每个人都改变了你就可以转储php调用。

只需重用php实现…确保php的crypt库在您的系统环境路径中…

您可能需要更新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()与BSD中用于passwd crypt的相同。 我不知道这是否是一个dejure标准,但它是事实上的标准。 所以。

我支持我的立场,它似乎并不是微不足道的。

您可以考虑保持旧的PHP运行,而不是移植代码,并严格使用它来validation旧密码的密码。 当用户更改密码时,请使用新的哈希算法,这种算法更加“开放”。 您必须存储哈希,以及每个用户的“哈希风格”。