其实已经有比较全面的 nuget 包了 https://github.com/geel9/SteamAuth (ArchiSteamFarm 内部也实现了验证码生成,但是 ASF 太庞大了,代码一时抄看不过来,SteamAuth 代码比较简单),由于我只需要生成验证码,使用整个库太重,而且还需要添加 Newtonsoft.Json 这个额外包,所以只把生成验证码的逻辑抄整理出来
没有仔细了解过 TOTP,也许跟 TOTP 的生成方法一样,这里直接贴代码
using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Text; namespace SteamTest { internal class Program { private static byte[] steamGuardCodeTranslations = new byte[] { 50, 51, 52, 53, 54, 55, 56, 57, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81, 82, 84, 86, 87, 88, 89 }; private static string SharedSecret = ""; // shared_secret from Steam OAuth manifest file (.maFile) public static string TWO_FACTOR_TIME_QUERY = "https://api.steampowered.com/ITwoFactorService/QueryTime/v0001"; static async Task Main() { Console.WriteLine(GenerateSteamGuardCodeForTime(GetSystemUnixTime() + await GetTimeGapAsync())); } // From https://github.com/geel9/SteamAuth/blob/e9c224f53d4e656cc93adcbcf10cd91d08910202/SteamAuth/SteamGuardAccount.cs#L85 public static string GenerateSteamGuardCodeForTime(long time) { if (SharedSecret == null || SharedSecret.Length == 0) { return ""; } string sharedSecretUnescaped = Regex.Unescape(SharedSecret); byte[] sharedSecretArray = Convert.FromBase64String(sharedSecretUnescaped); byte[] timeArray = new byte[8]; time /= 30L; for (int i = 8; i > 0; i--) { timeArray[i - 1] = (byte)time; time >>= 8; } HMACSHA1 hmacGenerator = new HMACSHA1(); hmacGenerator.Key = sharedSecretArray; byte[] hashedData = hmacGenerator.ComputeHash(timeArray); byte[] codeArray = new byte[5]; try { byte b = (byte)(hashedData[19] & 0xF); int codePoint = (hashedData[b] & 0x7F) << 24 | (hashedData[b + 1] & 0xFF) << 16 | (hashedData[b + 2] & 0xFF) << 8 | (hashedData[b + 3] & 0xFF); for (int i = 0; i < 5; ++i) { codeArray[i] = steamGuardCodeTranslations[codePoint % steamGuardCodeTranslations.Length]; codePoint /= steamGuardCodeTranslations.Length; } } catch (Exception) { return null; //Change later, catch-alls are bad! } return Encoding.UTF8.GetString(codeArray); } public static long GetSystemUnixTime() { return (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; } public static async Task<long> GetTimeGapAsync() { long currentTime = GetSystemUnixTime(); HttpClient client = new HttpClient(); var response = await client.PostAsync(TWO_FACTOR_TIME_QUERY, null); string content = await response.Content.ReadAsStringAsync(); long serverTime = Convert.ToInt64(content.Split("server_time\":\"")[1].Split('"')[0]); return serverTime - currentTime; } } }
将 SteamAuth 中使用的 WebClient 替换成了 HttpClient