Files
douyin/utils/DouyinABogusSigner.cs

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);
}
}