233 lines
9.7 KiB
C#
233 lines
9.7 KiB
C#
using System.Buffers.Binary;
|
|
using System.Text;
|
|
|
|
namespace dy.net.utils
|
|
{
|
|
/// <summary>
|
|
/// Generates the a_bogus value used by Douyin's web search endpoint.
|
|
/// The implementation is self-contained so fnOS packages do not need Node or a browser runtime.
|
|
/// </summary>
|
|
public sealed class DouyinABogusSigner
|
|
{
|
|
private const string UserAgent = DouyinRequestParamManager.DY_USER_AGENT;
|
|
private const string Browser = "1536|742|1536|864|0|0|0|0|1536|864|1536|864|1536|742|24|24|Win32";
|
|
private const string AlphabetS3 = "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe";
|
|
private const string AlphabetS4 = "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe";
|
|
|
|
private static readonly uint[] InitialRegisters =
|
|
{
|
|
1937774191, 1226093241, 388252375, 3666478592,
|
|
2842636476, 372324522, 3817729613, 2969243214
|
|
};
|
|
|
|
private readonly byte[] _userAgentCode;
|
|
private readonly int[] _browserCode = Browser.Select(x => (int)x).ToArray();
|
|
|
|
public DouyinABogusSigner()
|
|
{
|
|
var encrypted = Rc4Encrypt(UserAgent, "\0\x01\x0e");
|
|
_userAgentCode = Sm3Hash(Encoding.UTF8.GetBytes(GenerateResult(encrypted, AlphabetS3)));
|
|
}
|
|
|
|
public string Sign(string queryString)
|
|
{
|
|
var start = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
|
var end = start + Random.Shared.Next(4, 9);
|
|
return Sign(queryString, "GET", start, end,
|
|
Random.Shared.NextDouble() * 10000,
|
|
Random.Shared.NextDouble() * 10000,
|
|
Random.Shared.NextDouble() * 10000);
|
|
}
|
|
|
|
/// <summary>Deterministic overload used by protocol fixture tests.</summary>
|
|
public string Sign(
|
|
string queryString,
|
|
string method,
|
|
long startTimeMilliseconds,
|
|
long endTimeMilliseconds,
|
|
double randomNumber1,
|
|
double randomNumber2,
|
|
double randomNumber3)
|
|
{
|
|
if (queryString == null) throw new ArgumentNullException(nameof(queryString));
|
|
var prefix = FromCharCodes(RandomList(randomNumber1, 170, 85, 1, 2, 5, 40))
|
|
+ FromCharCodes(RandomList(randomNumber2, 170, 85, 1, 0, 0, 0))
|
|
+ FromCharCodes(RandomList(randomNumber3, 170, 85, 1, 0, 5, 0));
|
|
var payload = GeneratePayload(queryString, method, startTimeMilliseconds, endTimeMilliseconds);
|
|
return GenerateResult(prefix + payload, AlphabetS4);
|
|
}
|
|
|
|
private string GeneratePayload(string queryString, string method, long start, long end)
|
|
{
|
|
var parameterCode = Sm3Hash(Sm3Hash(Encoding.UTF8.GetBytes(queryString + "cus")));
|
|
var methodCode = Sm3Hash(Sm3Hash(Encoding.UTF8.GetBytes((method ?? "GET") + "cus")));
|
|
var values = BuildPayloadValues(
|
|
(int)((end >> 24) & 255), parameterCode[21], _userAgentCode[23],
|
|
(int)((end >> 16) & 255), parameterCode[22], _userAgentCode[24],
|
|
(int)((end >> 8) & 255), (int)(end & 255),
|
|
(int)((start >> 24) & 255), (int)((start >> 16) & 255),
|
|
(int)((start >> 8) & 255), (int)(start & 255),
|
|
methodCode[21], methodCode[22],
|
|
(int)(end / 4294967296d), (int)(start / 4294967296d), Browser.Length);
|
|
var check = values.Aggregate(0, (current, value) => current ^ value);
|
|
values.AddRange(_browserCode);
|
|
values.Add(check);
|
|
return Rc4Encrypt(FromCharCodes(values), "y");
|
|
}
|
|
|
|
private static List<int> BuildPayloadValues(
|
|
int a, int b, int c, int d, int e, int f, int g, int h,
|
|
int i, int j, int k, int m, int n, int o, int p, int q, int r) =>
|
|
new()
|
|
{
|
|
44, a, 0, 0, 0, 0, 24, b, n, 0, c, d, 0, 0, 0, 1, 0, 239,
|
|
e, o, f, g, 0, 0, 0, 0, h, 0, 0, 14, i, j, 0, k, m, 3, p, 1,
|
|
q, 1, r, 0, 0, 0
|
|
};
|
|
|
|
private static IEnumerable<int> RandomList(
|
|
double randomNumber, int evenMask, int oddMask, int evenLow, int oddLow, int evenHigh, int oddHigh)
|
|
{
|
|
var value = (int)randomNumber;
|
|
var low = value & 255;
|
|
var high = value >> 8;
|
|
return new[]
|
|
{
|
|
(low & evenMask) | evenLow,
|
|
(low & oddMask) | oddLow,
|
|
(high & evenMask) | evenHigh,
|
|
(high & oddMask) | oddHigh
|
|
};
|
|
}
|
|
|
|
private static string FromCharCodes(IEnumerable<int> values) =>
|
|
new(values.Select(value => (char)value).ToArray());
|
|
|
|
private static string Rc4Encrypt(string plaintext, string key)
|
|
{
|
|
var state = Enumerable.Range(0, 256).ToArray();
|
|
var j = 0;
|
|
for (var i = 0; i < 256; i++)
|
|
{
|
|
j = (j + state[i] + key[i % key.Length]) % 256;
|
|
(state[i], state[j]) = (state[j], state[i]);
|
|
}
|
|
|
|
var output = new char[plaintext.Length];
|
|
var x = 0;
|
|
j = 0;
|
|
for (var index = 0; index < plaintext.Length; index++)
|
|
{
|
|
x = (x + 1) % 256;
|
|
j = (j + state[x]) % 256;
|
|
(state[x], state[j]) = (state[j], state[x]);
|
|
var keyByte = state[(state[x] + state[j]) % 256];
|
|
output[index] = (char)(keyByte ^ plaintext[index]);
|
|
}
|
|
return new string(output);
|
|
}
|
|
|
|
private static string GenerateResult(string value, string alphabet)
|
|
{
|
|
var result = new StringBuilder();
|
|
for (var index = 0; index < value.Length; index += 3)
|
|
{
|
|
var number = value[index] << 16;
|
|
if (index + 1 < value.Length) number |= value[index + 1] << 8;
|
|
if (index + 2 < value.Length) number |= value[index + 2];
|
|
|
|
result.Append(alphabet[(number & 0xfc0000) >> 18]);
|
|
result.Append(alphabet[(number & 0x03f000) >> 12]);
|
|
if (index + 1 < value.Length) result.Append(alphabet[(number & 0x0fc0) >> 6]);
|
|
if (index + 2 < value.Length) result.Append(alphabet[number & 0x3f]);
|
|
}
|
|
while (result.Length % 4 != 0) result.Append('=');
|
|
return result.ToString();
|
|
}
|
|
|
|
private static byte[] Sm3Hash(byte[] data)
|
|
{
|
|
var bitLength = (ulong)data.LongLength * 8;
|
|
var paddedLength = ((data.Length + 1 + 8 + 63) / 64) * 64;
|
|
var padded = new byte[paddedLength];
|
|
Buffer.BlockCopy(data, 0, padded, 0, data.Length);
|
|
padded[data.Length] = 0x80;
|
|
BinaryPrimitives.WriteUInt64BigEndian(padded.AsSpan(padded.Length - 8), bitLength);
|
|
|
|
var registers = (uint[])InitialRegisters.Clone();
|
|
for (var offset = 0; offset < padded.Length; offset += 64)
|
|
Compress(registers, padded.AsSpan(offset, 64));
|
|
|
|
var result = new byte[32];
|
|
for (var index = 0; index < registers.Length; index++)
|
|
BinaryPrimitives.WriteUInt32BigEndian(result.AsSpan(index * 4, 4), registers[index]);
|
|
return result;
|
|
}
|
|
|
|
private static void Compress(uint[] registers, ReadOnlySpan<byte> block)
|
|
{
|
|
var words = new uint[68];
|
|
var expanded = new uint[64];
|
|
for (var index = 0; index < 16; index++)
|
|
words[index] = BinaryPrimitives.ReadUInt32BigEndian(block.Slice(index * 4, 4));
|
|
for (var index = 16; index < 68; index++)
|
|
{
|
|
var value = words[index - 16] ^ words[index - 9] ^ RotateLeft(words[index - 3], 15);
|
|
words[index] = P1(value) ^ RotateLeft(words[index - 13], 7) ^ words[index - 6];
|
|
}
|
|
for (var index = 0; index < 64; index++) expanded[index] = words[index] ^ words[index + 4];
|
|
|
|
var a = registers[0];
|
|
var b = registers[1];
|
|
var c = registers[2];
|
|
var d = registers[3];
|
|
var e = registers[4];
|
|
var f = registers[5];
|
|
var g = registers[6];
|
|
var h = registers[7];
|
|
|
|
for (var index = 0; index < 64; index++)
|
|
{
|
|
var rotatedA = RotateLeft(a, 12);
|
|
var ss1 = RotateLeft(unchecked(rotatedA + e + RotateLeft(index < 16 ? 0x79cc4519u : 0x7a879d8au, index)), 7);
|
|
var ss2 = ss1 ^ rotatedA;
|
|
var tt1 = unchecked(Ff(index, a, b, c) + d + ss2 + expanded[index]);
|
|
var tt2 = unchecked(Gg(index, e, f, g) + h + ss1 + words[index]);
|
|
d = c;
|
|
c = RotateLeft(b, 9);
|
|
b = a;
|
|
a = tt1;
|
|
h = g;
|
|
g = RotateLeft(f, 19);
|
|
f = e;
|
|
e = P0(tt2);
|
|
}
|
|
|
|
registers[0] ^= a;
|
|
registers[1] ^= b;
|
|
registers[2] ^= c;
|
|
registers[3] ^= d;
|
|
registers[4] ^= e;
|
|
registers[5] ^= f;
|
|
registers[6] ^= g;
|
|
registers[7] ^= h;
|
|
}
|
|
|
|
private static uint RotateLeft(uint value, int count)
|
|
{
|
|
count &= 31;
|
|
return count == 0 ? value : (value << count) | (value >> (32 - count));
|
|
}
|
|
|
|
private static uint P0(uint value) => value ^ RotateLeft(value, 9) ^ RotateLeft(value, 17);
|
|
|
|
private static uint P1(uint value) => value ^ RotateLeft(value, 15) ^ RotateLeft(value, 23);
|
|
|
|
private static uint Ff(int index, uint x, uint y, uint z) =>
|
|
index < 16 ? x ^ y ^ z : (x & y) | (x & z) | (y & z);
|
|
|
|
private static uint Gg(int index, uint x, uint y, uint z) =>
|
|
index < 16 ? x ^ y ^ z : (x & y) | (~x & z);
|
|
}
|
|
}
|