using System; using System.Collections.Generic; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace MegghysAPI.Modules { internal static class GeetestCrypto { private const string RsaN = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"; private const string RsaE = "010001"; private const string AesKey = "1234567890123456"; private static readonly byte[] AesIv = { 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48 }; private const string Base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()"; private const int Mask1 = 7274496; private const int Mask2 = 9483264; private const int Mask3 = 19220; private const int Mask4 = 235; public static string ClickCalculate(string key, string gt, string challenge) { if (string.IsNullOrEmpty(challenge) || challenge.Length < 2) { throw new ArgumentException("challenge 长度必须至少为 2", nameof(challenge)); } var passTime = (int)(Random.Shared.NextDouble() * 700.0 + 1300.0); var challengePrefix = challenge[..^2]; var md5Source = string.Concat(gt, challengePrefix, passTime.ToString()); var rpBytes = MD5.HashData(Encoding.UTF8.GetBytes(md5Source)); var rp = Convert.ToHexString(rpBytes).ToLowerInvariant(); var payload = new Dictionary { ["lang"] = "zh-cn", ["passtime"] = passTime, ["a"] = key, ["tt"] = string.Empty, ["ep"] = BuildEpObject(), ["h9s9"] = "1816378497", ["rp"] = rp, }; var json = JsonSerializer.Serialize(payload); return Encrypt(json); } public static string SlideCalculate(int distance, string gt, string challenge, ReadOnlySpan c, string s) { if (distance < 0) { throw new ArgumentOutOfRangeException(nameof(distance), "distance 必须大于等于 0"); } if (string.IsNullOrEmpty(challenge) || challenge.Length < 2) { throw new ArgumentException("challenge 长度必须至少为 2", nameof(challenge)); } var track = GetSlideTrack(distance); var passTime = track[track.Count - 1][2]; var encryptedTrack = TrackEncrypt(track); var aa = FinalEncrypt(encryptedTrack, c, s); var userResponse = UserResponse(distance, challenge); var challengePrefix = challenge[..^2]; var md5Source = string.Concat(gt, challengePrefix, passTime.ToString()); var rpBytes = MD5.HashData(Encoding.UTF8.GetBytes(md5Source)); var rp = Convert.ToHexString(rpBytes).ToLowerInvariant(); var payload = new Dictionary { ["lang"] = "zh-cn", ["userresponse"] = userResponse, ["passtime"] = passTime, ["imgload"] = NextIntInclusive(100, 200), ["aa"] = aa, ["ep"] = BuildEpObject(), ["rp"] = rp, }; var json = JsonSerializer.Serialize(payload); return Encrypt(json); } private static Dictionary BuildEpObject() { return new Dictionary { ["v"] = "9.1.8-bfget5", ["$_E_"] = false, ["me"] = true, ["ven"] = "Google Inc. (Intel)", ["ren"] = "ANGLE (Intel, Intel(R) HD Graphics 520 Direct3D11 vs_5_0 ps_5_0, D3D11)", ["fp"] = new object[] { "move", 483, 149, 1702019849214L, "pointermove" }, ["lp"] = new object[] { "up", 657, 100, 1702019852230L, "pointerup" }, ["em"] = new Dictionary { ["ph"] = 0, ["cp"] = 0, ["ek"] = "11", ["wd"] = 1, ["nt"] = 0, ["si"] = 0, ["sc"] = 0, }, ["tm"] = new Dictionary { ["a"] = 1702019845759L, ["b"] = 1702019845951L, ["c"] = 1702019845951L, ["d"] = 0, ["e"] = 0, ["f"] = 1702019845763L, ["g"] = 1702019845785L, ["h"] = 1702019845785L, ["i"] = 1702019845785L, ["j"] = 1702019845845L, ["k"] = 1702019845812L, ["l"] = 1702019845845L, ["m"] = 1702019845942L, ["n"] = 1702019845946L, ["o"] = 1702019845954L, ["p"] = 1702019846282L, ["q"] = 1702019846282L, ["r"] = 1702019846287L, ["s"] = 1702019846288L, ["t"] = 1702019846288L, ["u"] = 1702019846288L, }, ["dnf"] = "dnf", ["by"] = 0, }; } private static string Encrypt(string json) { var rsaEncryptedKey = RsaEncrypt(AesKey); var aesEncryptedData = AesEncrypt(json); var base64Part = CustomBase64(aesEncryptedData); return base64Part + rsaEncryptedKey; } private static byte[] AesEncrypt(string data) { using var aes = Aes.Create(); aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7; aes.Key = Encoding.UTF8.GetBytes(AesKey); aes.IV = AesIv; using var encryptor = aes.CreateEncryptor(); var inputBytes = Encoding.UTF8.GetBytes(data); return encryptor.TransformFinalBlock(inputBytes, 0, inputBytes.Length); } private static string RsaEncrypt(string data) { var modulus = TrimLeadingZero(HexToBytes(RsaN)); var exponent = TrimLeadingZero(HexToBytes(RsaE)); using var rsa = RSA.Create(); rsa.ImportParameters(new RSAParameters { Modulus = modulus, Exponent = exponent }); var bytes = Encoding.UTF8.GetBytes(data); var encrypted = rsa.Encrypt(bytes, RSAEncryptionPadding.Pkcs1); return Convert.ToHexString(encrypted).ToLowerInvariant(); } private static byte[] TrimLeadingZero(byte[] value) { if (value.Length > 0 && value[0] == 0) { var trimmed = new byte[value.Length - 1]; Buffer.BlockCopy(value, 1, trimmed, 0, trimmed.Length); return trimmed; } return value; } private static string CustomBase64(ReadOnlySpan input) { var sb = new StringBuilder(); var padding = string.Empty; var len = input.Length; var ptr = 0; while (ptr < len) { if (ptr + 2 < len) { var c = (input[ptr] << 16) + (input[ptr + 1] << 8) + input[ptr + 2]; sb.Append(Base64Table[GetIntByMask(c, Mask1)]); sb.Append(Base64Table[GetIntByMask(c, Mask2)]); sb.Append(Base64Table[GetIntByMask(c, Mask3)]); sb.Append(Base64Table[GetIntByMask(c, Mask4)]); } else { var remainder = len % 3; if (remainder == 2) { var c = (input[ptr] << 16) + (input[ptr + 1] << 8); sb.Append(Base64Table[GetIntByMask(c, Mask1)]); sb.Append(Base64Table[GetIntByMask(c, Mask2)]); sb.Append(Base64Table[GetIntByMask(c, Mask3)]); padding = "."; } else if (remainder == 1) { var c = input[ptr] << 16; sb.Append(Base64Table[GetIntByMask(c, Mask1)]); sb.Append(Base64Table[GetIntByMask(c, Mask2)]); padding = ".."; } } ptr += 3; } sb.Append(padding); return sb.ToString(); } private static int GetIntByMask(int value, int mask) { var result = 0; for (var bit = 23; bit >= 0; bit--) { if (ChooseBit(mask, bit) == 1) { result = (result << 1) | ChooseBit(value, bit); } } return result; } private static int ChooseBit(int value, int bit) { return (value >> bit) & 1; } private static byte[] HexToBytes(string hex) { var cleaned = hex.Replace("\n", string.Empty) .Replace("\r", string.Empty) .Replace(" ", string.Empty); if (cleaned.Length % 2 != 0) { throw new ArgumentException("十六进制字符串长度必须为偶数", nameof(hex)); } var bytes = new byte[cleaned.Length / 2]; for (var i = 0; i < bytes.Length; i++) { bytes[i] = Convert.ToByte(cleaned.Substring(i * 2, 2), 16); } return bytes; } private static int NextIntInclusive(int minValue, int maxValue) { if (minValue > maxValue) { throw new ArgumentException("minValue 必须小于等于 maxValue"); } return Random.Shared.Next(minValue, maxValue + 1); } private static List> GetSlideTrack(int distance) { if (distance < 0) { throw new ArgumentOutOfRangeException(nameof(distance)); } var slideTrack = new List>(); var x1 = NextIntInclusive(-50, -10); var y1 = NextIntInclusive(-50, -10); slideTrack.Add(new List { x1, y1, 0 }); slideTrack.Add(new List { 0, 0, 0 }); var count = 30 + distance / 2; var t = NextIntInclusive(50, 100); var currentX = 0; var currentY = 0; for (var i = 0; i < count; i++) { var sep = i / (double)count; double x; if (Math.Abs(sep - 1.0) < double.Epsilon) { x = distance; } else { x = (1.0 - Math.Pow(2.0, -10.0 * sep)) * distance; } var xRounded = (int)Math.Round(x); t += NextIntInclusive(10, 20); if (xRounded == currentX) { continue; } slideTrack.Add(new List { xRounded, currentY, t }); currentX = xRounded; } var last = slideTrack[slideTrack.Count - 1]; slideTrack.Add(new List(last)); return slideTrack; } private static string TrackEncrypt(List> track) { static List> ProcessTrack(List> trackData) { var result = new List>(); var o = 0; for (var s = 0; s < trackData.Count - 1; s++) { var e = trackData[s + 1][0] - trackData[s][0]; var n = trackData[s + 1][1] - trackData[s][1]; var r = trackData[s + 1][2] - trackData[s][2]; if (e == 0 && n == 0 && r == 0) { continue; } if (e == 0 && n == 0) { o += r; } else { result.Add(new List { e, n, r + o }); o = 0; } } if (o != 0 && result.Count > 0) { var lastItem = result[result.Count - 1]; result.Add(new List { lastItem[0], lastItem[1], o }); } return result; } static string EncodeValue(int t) { const string chars = "()*,-./0123456789:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqr"; var n = chars.Length; var r = new StringBuilder(); var i = Math.Abs(t); var oVal = i / n; var o = oVal >= n ? n - 1 : oVal; if (o >= chars.Length) { o = chars.Length - 1; } if (o > 0) { r.Append(chars[o]); } var s = new StringBuilder(); if (t < 0) { s.Append('!'); } if (r.Length > 0) { s.Append('$'); } s.Append(r); s.Append(chars[i % n]); return s.ToString(); } static char? EncodePair(IReadOnlyList values) { var pairs = new List { new[] { 1, 0 }, new[] { 2, 0 }, new[] { 1, -1 }, new[] { 1, 1 }, new[] { 0, 1 }, new[] { 0, -1 }, new[] { 3, 0 }, new[] { 2, -1 }, new[] { 2, 1 }, }; const string chars = "stuvwxyz~"; for (var idx = 0; idx < pairs.Count; idx++) { var pair = pairs[idx]; if (values[0] == pair[0] && values[1] == pair[1]) { return chars[idx]; } } return null; } static void ProcessElement(IReadOnlyList item, StringBuilder r, StringBuilder i, StringBuilder o) { var encodedPair = EncodePair(item); if (encodedPair.HasValue) { i.Append(encodedPair.Value); } else { r.Append(EncodeValue(item[0])); i.Append(EncodeValue(item[1])); } o.Append(EncodeValue(item[2])); } var processed = ProcessTrack(track); var rBuilder = new StringBuilder(); var iBuilder = new StringBuilder(); var oBuilder = new StringBuilder(); foreach (var item in processed) { ProcessElement(item, rBuilder, iBuilder, oBuilder); } return string.Concat(rBuilder, "!!", iBuilder, "!!", oBuilder); } private static string FinalEncrypt(string t, ReadOnlySpan e, string n) { if (e.Length < 5 || string.IsNullOrEmpty(n)) { return t; } var s = e[0]; var a = e[2]; var m = e[4]; var originalLength = t.Length; var builder = new StringBuilder(t); var index = 0; while (index <= n.Length - 2) { var hexPair = n.Substring(index, 2); index += 2; var c = Convert.ToByte(hexPair, 16); var u = (char)c; var ll = ((ulong)s * c * c + (ulong)a * c + m) % (ulong)originalLength; builder.Insert((int)ll, u); } return builder.ToString(); } private static string UserResponse(int key, string challenge) { if (challenge.Length < 2) { throw new ArgumentException("challenge 长度必须至少为 2", nameof(challenge)); } var chars = challenge.ToCharArray(); var lastTwo = new[] { chars[chars.Length - 2], chars[chars.Length - 1] }; var r = new List(); foreach (var c in lastTwo) { var code = (int)c; r.Add(code > 57 ? code - 87 : code - 48); } var n = 36 * r[0] + r[1]; var a = key + n; var underscores = new List> { new List(), new List(), new List(), new List(), new List() }; var charSet = new HashSet(); var idx = 0; for (var i = 0; i < chars.Length - 2; i++) { var c = chars[i]; if (charSet.Add(c)) { underscores[idx].Add(c); idx = (idx + 1) % 5; } } var f = a; var d = 4; var result = new StringBuilder(); var weights = new List { 1, 2, 5, 10, 50 }; while (f > 0) { if (d >= weights.Count) { throw new InvalidOperationException("权重数组越界,请检查输入参数有效性"); } if (f >= weights[d]) { if (underscores[d].Count == 0) { throw new InvalidOperationException($"五元组数组 {d} 号位置无可用字符,请确保输入字符串包含足够多的唯一字符"); } result.Append(underscores[d][0]); f -= weights[d]; } else { underscores.RemoveAt(d); weights.RemoveAt(d); if (d > 0) { d -= 1; } else { throw new InvalidOperationException("权重数组耗尽,无法继续处理"); } } } return result.ToString(); } } }