gfd2 Asked:2022-04-15 18:09:12 +0000 UTC2022-04-15 18:09:12 +0000 UTC 2022-04-15 18:09:12 +0000 UTC 使用 Apache 特定的 salted MD5 算法生成密码哈希 772 您需要从 C# 中的登录名和密码中获取此字符串。 test:$apr1$zg40inu2$fb4NUdl7Gj4yfKIwWJFt// 此行包含用户名和密码test。想不通。 c# 1 个回答 Voted Best Answer aepot 2022-04-15T22:19:53Z2022-04-15T22:19:53Z 这是一次伟大的冒险,谢谢。为什么 Apache 开发人员这样做并不完全清楚,但可能是为了保护不安全的 MD5。 文档: Apache 特定算法使用随机 32 位盐和密码的各种组合的迭代(1,000 次)MD5 摘要。有关算法的详细信息,请参见 APR 源文件apr_md5.c。 我通过将源代码转换为 PHP收到了此代码。 static void Main(string[] args) { string src = "$apr1$zg40inu2$fb4NUdl7Gj4yfKIwWJFt//"; string[] hashed = src.Split('$'); string pass = "test"; string salt = hashed[2]; Console.WriteLine(src); Console.WriteLine(CryptApr1MD5(pass, salt)); Console.ReadKey(); } static string CryptApr1MD5(string password, string salt = null) { const string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const string outputChars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; if (salt is null) { Random rnd = new Random(); salt = string.Concat(Enumerable.Range(0, 8).Select(_ => outputChars[rnd.Next(0, outputChars.Length)])); } byte[] saltBytes = Encoding.ASCII.GetBytes(salt); byte[] passBytes = Encoding.ASCII.GetBytes(password); List<byte> tmp = new List<byte>(Encoding.ASCII.GetBytes($"{password}$apr1${salt}")); MD5 md5 = MD5.Create(); byte[] hashBytes = md5.ComputeHash(Encoding.ASCII.GetBytes(password + salt + password)); for (int i = password.Length; i > 0; i -= 16) tmp.AddRange(hashBytes.Take(Math.Min(16, i))); for (int i = password.Length; i > 0; i >>= 1) tmp.Add((i & 1) == 1 ? (byte)0 : passBytes[0]); hashBytes = md5.ComputeHash(tmp.ToArray()); tmp.Clear(); for (int i = 0; i < 1000; i++) { tmp.AddRange((i & 1) == 1 ? passBytes : hashBytes); if (i % 3 != 0) tmp.AddRange(saltBytes); if (i % 7 != 0) tmp.AddRange(passBytes); tmp.AddRange((i & 1) == 1 ? hashBytes : passBytes); hashBytes = md5.ComputeHash(tmp.ToArray()); tmp.Clear(); } tmp.Add(0); tmp.Add(0); tmp.Add(hashBytes[11]); for (int i = 4; i >= 0; i--) { tmp.Add(hashBytes[i]); tmp.Add(hashBytes[i + 6]); tmp.Add(hashBytes[i == 4 ? 5 : i + 12]); } string result = string.Concat(Convert.ToBase64String(tmp.ToArray()).Skip(2).Reverse().Select(c => outputChars[base64Chars.IndexOf(c)])); return $"$apr1${salt}${result}"; } 控制台输出 $apr1$zg40inu2$fb4NUdl7Gj4yfKIwWJFt// $apr1$zg40inu2$fb4NUdl7Gj4yfKIwWJFt// 替代实现,大约快 20%,需要 C# 8.0。 static string CryptApr1MD52(string password, string salt = null) { const string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const string outputChars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; const string signature = "$apr1$"; if (salt is null) { salt = string.Create(8, new Random(), (span, rnd) => { for (int i = 0; i < span.Length; i++) span[i] = outputChars[rnd.Next(0, outputChars.Length)]; }); } byte[] saltBytes = Encoding.ASCII.GetBytes(salt); byte[] passBytes = Encoding.ASCII.GetBytes(password); byte[] signBytes = Encoding.ASCII.GetBytes(signature); using var ms = new MemoryStream(); MD5 md5 = MD5.Create(); ms.Write(passBytes); ms.Write(saltBytes); ms.Write(passBytes); byte[] hashBytes = md5.ComputeHash(ms.GetBuffer(), 0, (int)ms.Position); ms.SetLength(passBytes.Length); ms.Write(signBytes); ms.Write(saltBytes); for (int i = 0; i < passBytes.Length; i += 16) ms.Write(hashBytes, 0, Math.Min(passBytes.Length - i, 16)); for (int i = passBytes.Length; i > 0; i >>= 1) ms.WriteByte((i & 1) == 1 ? (byte)0 : passBytes[0]); hashBytes = md5.ComputeHash(ms.GetBuffer(), 0, (int)ms.Position); ms.SetLength(0); for (int i = 0; i < 1000; i++) { ms.Write((i & 1) == 1 ? passBytes : hashBytes); if (i % 3 != 0) ms.Write(saltBytes); if (i % 7 != 0) ms.Write(passBytes); ms.Write((i & 1) == 1 ? hashBytes : passBytes); hashBytes = md5.ComputeHash(ms.GetBuffer(), 0, (int)ms.Position); ms.SetLength(0); } ms.WriteByte(0); ms.WriteByte(0); ms.WriteByte(hashBytes[11]); for (int i = 4; i >= 0; i--) { ms.WriteByte(hashBytes[i]); ms.WriteByte(hashBytes[i + 6]); ms.WriteByte(hashBytes[i == 4 ? 5 : i + 12]); } return string.Create(37, (ms, salt), (span, t) => { signature.AsSpan().CopyTo(span); t.salt.AsSpan().CopyTo(span[6..]); span[14] = '$'; ReadOnlySpan<char> buffer = Convert.ToBase64String(t.ms.GetBuffer(), 0, (int)t.ms.Position); for (int i = 15; i < span.Length; i++) span[i] = outputChars[base64Chars.IndexOf(buffer[^(i - 14)])]; }); }
这是一次伟大的冒险,谢谢。为什么 Apache 开发人员这样做并不完全清楚,但可能是为了保护不安全的 MD5。
文档:
我通过将源代码转换为 PHP收到了此代码。
控制台输出
替代实现,大约快 20%,需要 C# 8.0。